diff --git a/webclient-vue/docs/README.md b/webclient-vue/docs/README.md new file mode 100644 index 0000000..41073a9 --- /dev/null +++ b/webclient-vue/docs/README.md @@ -0,0 +1,29 @@ +# SHServ Vue Client Specification + +This directory contains the technical specification for a new SHServ web client. + +The current `webclient/` implementation is the reference implementation. The new +client should preserve its functional behavior while moving to Vue and the +`gnexus-ui-kit` design system. + +## Documents + +- `overview.md` - goals, boundaries, architecture direction. +- `current-client-map.md` - current routes, screens, modules, and UI behavior. +- `api-contract.md` - API endpoints used by the client and expected response handling. +- `screens.md` - target screen-by-screen functional specification. +- `state-and-components.md` - current global helpers mapped to Vue stores/components. +- `scaffold.md` - recommended Vue/Vite project scaffold and first milestone. +- `coding-conventions.md` - code organization, state, API, and UI rules. +- `ui-kit-integration.md` - planned `gnexus-ui-kit` adapter strategy. +- `migration-plan.md` - phased plan for building the new client in parallel. + +## Working Rules + +- Keep the current `webclient/` operational until the new client covers the main flows. +- Treat backend API behavior as a contract; document gaps before changing endpoints. +- New UI code should be JavaScript, not TypeScript. +- New UI code should not depend on old global objects such as `Helper`, `Screens`, + `DataProvider`, `Toasts`, or `Modals`. +- Generated build artifacts for the new client should stay inside the new client + directory. diff --git a/webclient-vue/docs/api-contract.md b/webclient-vue/docs/api-contract.md new file mode 100644 index 0000000..ed72228 --- /dev/null +++ b/webclient-vue/docs/api-contract.md @@ -0,0 +1,123 @@ +# API Contract + +The new client should wrap all backend calls in a dedicated API layer. UI +components should not call `fetch` directly. + +## Transport + +Current dev transport: + +```text +GET|POST {baseUrl}/proxy.php?path=/api/v1/... +``` + +Current backend base from `webclient/config.php`: + +```text +http://smart-home-serv.local +``` + +## Common Response Shape + +Success: + +```json +{ + "status": true, + "data": {} +} +``` + +Error: + +```json +{ + "status": false, + "error_alias": "device_request_fail", + "failed_fields": [], + "msg": "Устройство не отвечает" +} +``` + +The new API layer should normalize responses into one result shape: + +```js +// success +{ ok: true, data, meta } + +// failure +{ ok: false, error, meta } +``` + +## Devices + +| Method | Path | Current Use | +| --- | --- | --- | +| GET | `/api/v1/devices/list` | Devices list. | +| GET | `/api/v1/devices/id/{id}` | Device DB info. | +| GET | `/api/v1/devices/id/{id}/info` | Device DB info plus live `/about`. | +| GET | `/api/v1/devices/id/{id}/status` | Live device state. | +| GET | `/api/v1/devices/scanning/all` | Scan all devices in local network range. | +| GET | `/api/v1/devices/scanning/setup` | Scan setup-mode devices. | +| POST | `/api/v1/devices/setup/new-device` | Register a setup-mode device. | +| GET | `/api/v1/devices/id/{id}/reboot` | Reboot one device. | +| POST | `/api/v1/devices/resetup` | Reset/rebind device token. | +| POST | `/api/v1/devices/reset` | Reset device. | +| POST | `/api/v1/devices/update-name` | Update device name. | +| POST | `/api/v1/devices/update-description` | Update device description. | +| POST | `/api/v1/devices/update-alias` | Update device alias. | +| POST | `/api/v1/devices/place-in-area` | Assign device to area. | +| GET | `/api/v1/devices/id/{id}/unassign-from-area` | Remove device from area. | + +Device state rendering depends on `device_type`: + +- `relay` expects `channels[]` with `id` and `state`. +- `button` expects `channels[]` with `id` and `indicator`. +- `sensor` expects `sensors.light`, `temperature`, `pressure`, `humidity`, + `radar`, and `microphone`. +- `hatch` expects `hatch.state` and `hatch.position_pct`. + +## Scripts + +| Method | Path | Current Use | +| --- | --- | --- | +| GET | `/api/v1/scripts/actions/list` | Action script cards. | +| POST | `/api/v1/scripts/actions/run` | Run action script by alias. | +| GET | `/api/v1/scripts/regular/list` | Regular scripts table. | +| GET | `/api/v1/scripts/regular/alias/{alias}/enable` | Enable regular script. | +| GET | `/api/v1/scripts/regular/alias/{alias}/disable` | Disable regular script. | +| GET | `/api/v1/scripts/scopes/list` | Scopes table. | +| GET | `/api/v1/scripts/scopes/name/{name}/enable` | Enable scope. | +| GET | `/api/v1/scripts/scopes/name/{name}/disable` | Disable scope. | +| GET | `/api/v1/scripts/scopes/name/{name}/remove` | Remove scope. | +| GET | `/api/v1/scripts/scopes/name/{filename}` | Get scope source by filename/name. | +| POST | `/api/v1/scripts/scopes/new` | Create scope. | +| POST | `/api/v1/scripts/scopes/update` | Update scope. | +| POST | `/api/v1/scripts/place-in-area` | Assign script to area. | +| GET | `/api/v1/scripts/id/{id}/unassign-from-area` | Remove script from area. | + +## Areas + +| Method | Path | Current Use | +| --- | --- | --- | +| GET | `/api/v1/areas/list` | Areas tree and favorites. | +| GET | `/api/v1/areas/id/{area_id}/list` | Area inner list. | +| POST | `/api/v1/areas/new-area` | Create area. | +| GET | `/api/v1/areas/id/{area_id}/remove` | Remove area. | +| POST | `/api/v1/areas/place-in-area` | Move area under another area. | +| POST | `/api/v1/areas/update-display-name` | Rename area display name. | +| POST | `/api/v1/areas/update-alias` | Update area alias. | +| GET | `/api/v1/areas/id/{area_id}/devices` | Devices in area. | +| GET | `/api/v1/areas/id/{area_id}/scripts` | Scripts in area. | +| GET | `/api/v1/areas/id/{id}/unassign-from-area` | Remove area from parent. | +| GET | `/api/v1/areas/types/list` | Allowed area types. | +| GET | `/api/v1/areas/reboot_devices` | Reboot all devices. | +| GET | `/api/v1/areas/id/{area_id}/reboot_devices` | Reboot devices in area. | + +## Reliability Requirements + +- Device status loading must be isolated per device. +- A failed device status request must not break the full screen. +- Device status requests should have a concurrency limit. +- The UI should distinguish backend error, network/proxy error, and device offline. +- API callback/consumer errors must not be treated as transport failures. diff --git a/webclient-vue/docs/coding-conventions.md b/webclient-vue/docs/coding-conventions.md new file mode 100644 index 0000000..1d8a8a3 --- /dev/null +++ b/webclient-vue/docs/coding-conventions.md @@ -0,0 +1,113 @@ +# Coding Conventions + +## Source Language + +Use JavaScript for the new client. + +Rules: + +- Backend response validation lives in `src/api/schemas`. +- UI-facing normalized models are produced by mapper functions. +- Avoid passing raw backend envelopes deep into UI components. +- If a response shape is uncertain, validate it at the API/module boundary and + return `invalid_response` on mismatch. + +## File Naming + +- Vue components: `PascalCase.vue`. +- Stores: `useXStore` exported from `stores/x.js`. +- Composables: `useThing.js`. +- API modules: lowercase domain names, e.g. `devices.js`. +- Runtime schemas: domain names, e.g. `devices.js`, `areas.js`. + +## Component Rules + +- Keep route components thin. +- Put reusable domain UI in `features/{domain}/components`. +- Put generic reusable UI in `components`. +- Do not pass raw backend DTO-like objects directly into deep UI components unless + the component is API-specific by design. +- Use props/events for reusable components. +- Use stores for screen-level data and cross-component state. + +## Store Rules + +- Stores own fetch/mutation orchestration. +- Stores call API modules. +- Stores expose loading/error states. +- Stores should not know about toasts or router navigation. +- Components or page-level actions decide how to show feedback. + +## API Rules + +- All API methods return normalized `{ ok, data?, error?, meta }` results. +- API modules do not show UI feedback. +- API modules do not mutate stores directly. +- API mappers should be pure functions. +- Request cancellation should use `AbortController`. + +## Error Handling + +Use this UI policy: + +- Route-level fetch failure: show route error state with retry. +- Per-row device status failure: show row-level error/offline state. +- Mutation failure: keep the modal/page open and show toast plus inline field + errors when available. +- Validation failure: show field-level errors before submitting. + +Do not: + +- Treat component render errors as network errors. +- Hide backend `msg` when present. +- Swallow failed mutations silently. + +## Formatting And Style + +- Use tabs or spaces consistently after scaffold decision. +- Keep components small enough that template, script, and styles are easy to scan. +- Prefer explicit names over abbreviations. +- Avoid direct DOM manipulation except for integration boundaries. +- Avoid global singletons unless they are intentional app-level services. + +## Icons + +Current client uses Phosphor Icons. + +New client options: + +- Continue using Phosphor if `gnexus-ui-kit` supports it or does not provide an + icon system. +- Prefer UI-kit icon primitives if they exist. + +Do not mix several icon systems in the same screen. + +## Dates And Formatting + +Centralize formatting in `src/utils/format.js` and `src/utils/dates.js`. + +Required helpers: + +- `formatDate` +- `formatDateTime` +- `formatTimeAgo` +- `formatDeviceConnectionStatus` +- `formatScriptState` + +## CSS And Styling + +- Prefer UI-kit tokens/classes. +- Put app-specific layout styles in `src/styles`. +- Avoid copying old SCSS wholesale. +- If old styles are needed temporarily, isolate them under a legacy namespace. +- Do not override UI-kit internals with brittle selectors. +- Keep UI-kit compatibility wrappers thin enough that design updates from + `gnexus-ui-kit` are visible without rewriting feature components. + +## Accessibility Baseline + +- Buttons must use real `