mirror of
https://gitee.com/pnoker/iot-dc3.git
synced 2026-08-28 22:21:16 +08:00
docs(web): document three-terminal UX architecture and SDK design
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
# dc3-sdk: Framework-Agnostic Client SDK (Design)
|
||||
|
||||
Status: proposed (phase 0 landed) · Scope: web / cli / future app · Drivers: A1 (device-independent semantics), boundary discipline in docs/design/frontend-three-terminal-ux.md
|
||||
|
||||
## Problem
|
||||
|
||||
The HTTP gateway contract is currently implemented twice:
|
||||
|
||||
- dc3-web/src/api/ (30 modules) — thin axios wrappers, coupled to
|
||||
`@/config/axios` (Element Plus notifications, router-guard 401 flow) and
|
||||
Vite `@/` aliases.
|
||||
- dc3-cli/src/core/client.ts — hand-rolled fetch calls for auth
|
||||
(token/salt, token/generate, token/cancel).
|
||||
|
||||
A native app (React Native) would need a third implementation. Every new
|
||||
endpoint, header, and error-code interpretation must be re-implemented per
|
||||
client — a standing violation of A1.
|
||||
|
||||
## Goal
|
||||
|
||||
One TypeScript SDK that implements the gateway contract exactly once and is
|
||||
consumed by web, CLI, and future app clients. The SDK is framework-agnostic:
|
||||
no Vue, no Element Plus, no vue-i18n, no axios hard-dependency.
|
||||
|
||||
## Package layout
|
||||
|
||||
```text
|
||||
dc3-sdk/
|
||||
├── src/
|
||||
│ ├── core/ http adapter contract, envelope R<T>, pagination,
|
||||
│ │ token/session lifecycle, error taxonomy
|
||||
│ ├── api/ one module per domain (driver/device/point/...),
|
||||
│ │ mirroring backend CRUD verbs (getXxx/listXxx/add/update/delete)
|
||||
│ ├── types/ PageQuery/PageResult, Record types, domain enums
|
||||
│ ├── i18n/ (optional) locale dictionaries as plain JSON
|
||||
│ └── index.ts
|
||||
├── tests/ vitest with an in-memory mock adapter
|
||||
└── package.json
|
||||
```
|
||||
|
||||
### Core contracts
|
||||
|
||||
```ts
|
||||
// HTTP transport abstraction — web injects axios, RN/node injects fetch.
|
||||
interface HttpClient {
|
||||
request<T>(config: {
|
||||
method: 'get' | 'post' | 'put' | 'delete';
|
||||
url: string;
|
||||
params?: Record<string, unknown>;
|
||||
data?: unknown;
|
||||
headers?: Record<string, string>;
|
||||
}): Promise<R<T>>;
|
||||
}
|
||||
|
||||
// Interceptors owned by the HOST, not the SDK: auth headers and 401
|
||||
// handling stay in the consuming app (web keeps its axios interceptors).
|
||||
interface SdkOptions {
|
||||
http: HttpClient;
|
||||
baseUrl: string;
|
||||
apiPrefix?: string; // default '/api/v3'
|
||||
onUnauthorized?: () => void; // host-provided redirect/logout policy
|
||||
}
|
||||
```
|
||||
|
||||
Auth/session: SDK exposes salt/generate/cancel/change-password and a
|
||||
`TokenStore` abstraction (web: httpOnly cookie — no-op; CLI: memory/file;
|
||||
app: Keychain via host adapter). The SDK never decides storage policy.
|
||||
|
||||
### Semantic layer (L1) portability
|
||||
|
||||
Entity schemas already became framework-free in this iteration:
|
||||
|
||||
- `EntityListConfig` and friends carry labels as translated strings
|
||||
produced by a `Translator = (key, params?) => string` — Vue's
|
||||
ComposerTranslation satisfies it structurally; 16 config modules no
|
||||
longer import vue-i18n.
|
||||
- Next step: move `EntityListConfig` type into the SDK's types layer so
|
||||
web and app render the same schema (configs stay in the host apps; the
|
||||
shape is shared).
|
||||
|
||||
Future option: backend-served JSON schemas via the OpenAPI export
|
||||
(`make openapi`) replace hand-authored configs without client changes.
|
||||
|
||||
## Boundary rules (lint-enforceable)
|
||||
|
||||
Inside dc3-sdk, imports of the following are forbidden:
|
||||
|
||||
- vue / vue-router / pinia / element-plus / vue-i18n
|
||||
- `@/config/*` host infrastructure
|
||||
- any browser-only global except behind `typeof window` guards
|
||||
|
||||
Enforce with eslint `no-restricted-imports` / `import/no-extraneous-dependencies`
|
||||
rules in the SDK package config.
|
||||
|
||||
## Migration plan
|
||||
|
||||
1. **Phase 0 (landed)**: de-framework L1 configs — `Translator` type,
|
||||
zero vue-i18n imports in the 16 entity config modules.
|
||||
2. **Phase 1**: scaffold `dc3-sdk` package (pnpm workspace), port
|
||||
`PageQuery/PageResult`, `R<T>` envelope, error taxonomy; vitest with
|
||||
mock adapter.
|
||||
3. **Phase 2**: port the 30 API wrapper modules; contract tests against
|
||||
the backend OpenAPI spec (or recorded fixtures).
|
||||
4. **Phase 3**: dc3-web consumes the SDK — delete `src/api`, keep the
|
||||
axios adapter + interceptors as the host glue; run full e2e (608 unit
|
||||
tests + Playwright) before removing anything.
|
||||
5. **Phase 4**: dc3-cli swaps its hand-rolled fetch auth for the SDK
|
||||
(removes the duplicated salt/generate/cancel flow).
|
||||
6. **Phase 5**: app client consumes the SDK with a fetch/Keychain adapter.
|
||||
|
||||
## Verification
|
||||
|
||||
- SDK unit tests run in node with zero DOM/Vue imports (CI assertion:
|
||||
`import ... from 'vue'` absent in SDK source).
|
||||
- Web regression gate: pnpm check + lint + full vitest + Playwright e2e.
|
||||
- CLI gate: dc3-cli vitest suites keep passing against a mock adapter.
|
||||
|
||||
## Revision
|
||||
|
||||
2026-08: v1 — proposal with Phase 0 landed (Translator de-frameworking,
|
||||
16 modules).
|
||||
@@ -0,0 +1,175 @@
|
||||
# Frontend Three-Terminal UX Architecture (ADR)
|
||||
|
||||
Status: accepted · Scope: dc3-web (and future dc3 app client) · Drivers: first-principles UX engineering
|
||||
|
||||
## Context
|
||||
|
||||
dc3-web is a Vue 3 management console for IoT operators. Its experience
|
||||
targets three device classes (desktop, tablet, mobile) and must later
|
||||
accommodate a native app as a fourth host of the mobile experience. The
|
||||
legacy codebase was desktop-first: a hard `min-width: 1280px` floor on
|
||||
`body` and the layout shell, fixed header columns, a fixed 220px settings
|
||||
aside, and no JavaScript breakpoint system.
|
||||
|
||||
Ad hoc responsive fixes (a few `@media` blocks, `el-col` responsive props on
|
||||
ten pages) proved that patches do not scale: every new page reintroduces the
|
||||
desktop assumptions. This ADR replaces patching with an architecture derived
|
||||
from first principles.
|
||||
|
||||
## First principles
|
||||
|
||||
Experience quality reduces to four physical quantities, valid on every
|
||||
device:
|
||||
|
||||
| Element | Question | Budget |
|
||||
|---|---|---|
|
||||
| Perceive | Does the user notice key state instantly? | < 100ms |
|
||||
| Understand | Is the information hierarchy readable at first glance? | first screen < 3s |
|
||||
| Act | Can the user complete a task with minimal motor cost? | fewest steps |
|
||||
| Feedback | Does every action answer visibly and predictably? | < 300ms |
|
||||
|
||||
Devices differ in exactly four variables: input modality (mouse vs thumb),
|
||||
viewport geometry, usage context (long sessions vs alarm response), and
|
||||
compute/bandwidth. The product is an operations console: state monitoring
|
||||
plus high-consequence actions over long sessions.
|
||||
|
||||
From these, the architecture derives seven axioms. Every axiom is a rule
|
||||
with a falsifiable violation signature so it can be enforced in review and
|
||||
CI.
|
||||
|
||||
## Axioms
|
||||
|
||||
**A1. Content semantics are device-independent.** Entity models, operation
|
||||
models, and schemas are authored once and consumed by every presentation
|
||||
host.
|
||||
*Violation signature:* a second copy of a field definition for a different
|
||||
device.
|
||||
|
||||
**A2. Presentation is rebuilt per device class, not shrunk.** Layout follows
|
||||
container geometry via fluid primitives (`minmax`, `auto-fit`, wrapping
|
||||
flex); coarse device-class switches are the only job of breakpoints.
|
||||
*Violation signature:* a growing pile of one-off `@media` patches.
|
||||
|
||||
**A3. Input capability decides interaction mode.** Hover, right-click, and
|
||||
inline editing are mouse language; 44x44 targets, bottom sheets, and swipes
|
||||
are thumb language. Pointer capability (fine/coarse, hover) is a runtime
|
||||
property, not a media-query guess — a tablet with a keyboard still deserves
|
||||
mouse interactions.
|
||||
*Violation signature:* hover-only actions, or touch targets below 44px.
|
||||
|
||||
**A4. Feedback latency is the perceived product.** 100ms synchronous
|
||||
feedback, <1s skeletons, optimistic updates beyond that.
|
||||
*Violation signature:* a button that does nothing until the network answers.
|
||||
|
||||
**A5. Consistency carries the user's memory.** Tokens -> components -> page
|
||||
templates, each with a single source of truth; no hardcoded colors, radii,
|
||||
or magic widths.
|
||||
*Violation signature:* non-token color/spacing literals in SCSS.
|
||||
|
||||
**A6. State is part of the experience.** Preferences (theme, density,
|
||||
locale), navigation position, and draft forms survive device and session
|
||||
boundaries.
|
||||
*Violation signature:* user context reset on device switch or refresh.
|
||||
|
||||
**A7. Accessibility is the quality baseline.** Contrast, keyboard focus,
|
||||
reduced motion, and zoom are non-negotiable — they double as the spec for
|
||||
outdoor/site inspection scenarios.
|
||||
|
||||
## Layer model
|
||||
|
||||
```text
|
||||
L1 Semantic entity model + operation model + schemas (summary/detail) device-independent, single truth
|
||||
L2 Tokens design tokens + breakpoints + theme (light/dark/auto) + density shared values
|
||||
L3 Components ~40 shared components, normalized behavior (loading/empty/error, a11y)
|
||||
L4 Patterns 4 page templates: monitor / list / detail / edit — three physical implementations each
|
||||
L5 Quality measurement -> gates -> regression -> revision (closed loop)
|
||||
```
|
||||
|
||||
Each layer depends only on the one below it. Changes propagate downward,
|
||||
never sideways.
|
||||
|
||||
## Boundary discipline (reuse contract)
|
||||
|
||||
The web UI shell (L3/L4) is not reusable by a native app; the layers below
|
||||
the rendering boundary are. To keep that reuse real, the following are
|
||||
forbidden in L1/L2 and in a future shared `dc3-sdk` package:
|
||||
|
||||
- imports of `@/config/*` Vue/Element Plus infrastructure (axios instance,
|
||||
`ElMessage`, vue-i18n `ComposerTranslation`);
|
||||
- Element Plus or Vue types in entity schema definitions (labels are i18n
|
||||
keys, not translation functions);
|
||||
- device-specific values in token sources (tokens are host-independent
|
||||
values; SCSS/CSS are one rendering of them).
|
||||
|
||||
Enforcement: ADR review + lint rules (no non-token literals in SCSS, no
|
||||
framework imports below L2).
|
||||
|
||||
## Breakpoint contract
|
||||
|
||||
Single contract, aligned with Element Plus `el-col` semantics (A5):
|
||||
|
||||
| Tier | Range | Terminal |
|
||||
|---|---|---|
|
||||
| xs | < 768px | mobile |
|
||||
| sm | 768 - 991px | tablet |
|
||||
| md | 992 - 1199px | tablet / small desktop |
|
||||
| lg | 1200 - 1919px | desktop |
|
||||
| xl | >= 1920px | wide desktop |
|
||||
|
||||
JavaScript (`useBreakpoint`) and CSS must both read this contract — no
|
||||
second, hand-rolled breakpoint set anywhere.
|
||||
|
||||
## Decisions (derived, not chosen)
|
||||
|
||||
1. Mobile navigation: drawer menu, not bottom tabs — the task distribution
|
||||
is long-tail (home/alarms frequent, 40+ settings pages rare), which
|
||||
bottom tabs model badly.
|
||||
2. Mobile tables: summary card lists driven by the L1 summary schema, not
|
||||
horizontal scrolling. Cross-row comparison — the table's purpose — is
|
||||
destroyed by horizontal panning; a sticky first column is the accepted
|
||||
interim state.
|
||||
3. Dark mode: in scope, as a product of L2 tokens, for long-session eye
|
||||
load and sharper alarm contrast, not as decoration.
|
||||
4. Visual layer: tokenized Element Plus, no bespoke component library —
|
||||
bespoke UI violates A5 economics (maintenance grows with component
|
||||
count).
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- No page-level horizontal scroll from 360px to 2560px (table containers
|
||||
exempt); `document.documentElement.scrollWidth <= window.innerWidth`.
|
||||
- Lighthouse mobile: perf >= 90, CLS <= 0.1, LCP <= 2.5s.
|
||||
- 100% touch targets >= 44x44; dialogs full-screen below 768px.
|
||||
- Zero hardcoded color/spacing literals in SCSS (token lint gate).
|
||||
- Playwright runs desktop, tablet, and mobile viewport projects; axe scan
|
||||
clean; visual regression on critical pages.
|
||||
|
||||
## Revision
|
||||
|
||||
2026-08: v1 — adopted with the L2 token/breakpoint/theme foundation
|
||||
(dc3-web src/styles/tokens.scss, theme.scss,
|
||||
src/composables/useBreakpoint.ts, src/store/modules/app.ts).
|
||||
|
||||
2026-08: v2 — shell three-terminal forms shipped: shared NavMenu (horizontal
|
||||
ellipsis / vertical drawer), Settings sidebar menu extracted and hosted in
|
||||
aside (desktop) / collapsed rail (tablet) / drawer (mobile), responsive
|
||||
login panels, compact mobile pagination in ToolCard. Verified via
|
||||
artifacts/viewport-check.mjs: zero page-level overflow and correct
|
||||
per-terminal DOM at 1440/834/390px viewports against the mock build.
|
||||
|
||||
2026-08: v3 — L1 de-frameworking (dc3-client-sdk Phase 0): Translator
|
||||
contract replaces vue-i18n ComposerTranslation across all 16 entity config
|
||||
modules; dc3-sdk extraction design documented in
|
||||
docs/design/dc3-client-sdk.md.
|
||||
|
||||
2026-08: v4 — measurement gate shipped: Playwright gains chromium-desktop
|
||||
(1440x900), chromium-tablet (834x1112, touch), and chromium-mobile
|
||||
(393x851, touch) projects; tests/e2e/specs/responsive.spec.ts gates the
|
||||
A2 overflow criterion (scrollWidth <= clientWidth on login/home/settings),
|
||||
A3 shell adaptation (menu strip vs drawer, aside vs drawer), and an A7
|
||||
accessible-name smoke probe. All 12 gate tests green against the mock
|
||||
build. CI (ci-web.yml) runs the gate automatically via pnpm test:e2e with
|
||||
chromium only. Fixes recorded: playwright 1.61.1/1.62.0 version split
|
||||
aligned to 1.62.0; ToolCard refresh/sort icon buttons gained aria-labels.
|
||||
Lighthouse budget and axe-core scans remain CI follow-ups (no new deps
|
||||
this iteration).
|
||||
Reference in New Issue
Block a user