forked from Qortal/q-blog
365 lines
15 KiB
Markdown
365 lines
15 KiB
Markdown
**TL;DR:** Below is a phase-by-phase implementation guide: what libraries we’ll use, what kinds of tests we’ll write, what contracts/functions we’ll define, and how we’ll validate each step. Still high-level—no filenames or code dropped—yet detailed enough to steer day-to-day work.
|
||
|
||
---
|
||
|
||
# Q-Blog → 1.0.0 Implementation Plan (Technical, High-Level)
|
||
|
||
## Phase 0 — Orientation & Quality Bar
|
||
|
||
**Libraries / tools**
|
||
|
||
- Issue labels & PR templates (tracker-native).
|
||
- Metrics: Web Vitals (`web-vitals`), synthetic a11y checks (`axe-core` via CI).
|
||
**Artifacts**
|
||
- **Quality charter**: SLOs (e.g., “save post success ≥ 99.9%”), accessibility bar (keyboard-only paths pass), perf budgets (LCP/INP).
|
||
- **Protected journeys** list: _read post, create post, edit post, manage blogs_.
|
||
**Validation**
|
||
- Signed-off charter; journeys ratified in a short doc.
|
||
|
||
---
|
||
|
||
## Phase 1 — Project Docs & Working Agreements
|
||
|
||
**Libraries / tools**
|
||
|
||
- Diagrams as code (Mermaid in docs renderer).
|
||
- Conventional commits (tooling optional).
|
||
**Artifacts**
|
||
- **Architecture map** (UI ↔ state ↔ API), **Testing pyramid**, **A11y standard**, **Contributing rules**, **Changelog categories**.
|
||
- **Decision Record template** (context → options → decision → impact).
|
||
**Validation**
|
||
- New contributor dry run: “can run + test + understand contracts in <30 min.”
|
||
|
||
---
|
||
|
||
## Phase 2 — Test Harness & Linting Baseline
|
||
|
||
**Libraries**
|
||
|
||
- Unit/Component: **Vitest**, **@testing-library/react**, **@testing-library/user-event**, **@testing-library/jest-dom**.
|
||
- A11y lint/checks: **eslint-plugin-jsx-a11y**, **axe-core** or **jest-axe**.
|
||
- Mocking: **MSW** (Mock Service Worker) for API.
|
||
- Types: TypeScript strict mode.
|
||
- Lint/format: **eslint** (flat config), **@typescript-eslint** plugin, **prettier**.
|
||
**Key setup**
|
||
- Global test setup (RTL matchers, MSW server, clock/timers).
|
||
- Coverage thresholds (e.g., 80/70/70 lines/branches/functions to start).
|
||
- CI lanes: install/cache → typecheck → lint → unit/component → (later) e2e smoke.
|
||
**Tests to seed**
|
||
- Smoke renders: home, post list, editor screen.
|
||
- Tiny interaction: typing in editor, enabling/disabling toolbar control, save button disabled until valid.
|
||
**Validation**
|
||
- Green **typecheck + lint + tests** on clean checkout; coverage reports generated.
|
||
|
||
---
|
||
|
||
## Phase 3 — Correctness Sweep
|
||
|
||
**Libraries**
|
||
|
||
- RTK ecosystem: **@reduxjs/toolkit** (already present), **RTK Query** for data-fetching (adds caching, retries, invalidation).
|
||
- Schema/runtime validation: **Zod** (form/io validation) **and** `zod-to-json-schema` for docs **or** **AJV** if we align with JSON Schema like Q-Chess. (Pick one; see “Contracts” below.)
|
||
**Contracts / functions**
|
||
- **Fetch layer** with cancellation & retries:
|
||
|
||
- `fetchJson<T>(req: Request, opts): Promise<Result<T>>`
|
||
- `withTimeout(controller, ms): AbortSignal`
|
||
|
||
- **Async state convention**: `{ status: 'idle'|'loading'|'success'|'error', data?, error? }`.
|
||
- **Error object**: `{ code: string; message: string; recoverable: boolean }`.
|
||
- **Validation**:
|
||
|
||
- For Zod: `const PostDto = z.object({ id: z.string(), ... })` → `parse(response)`.
|
||
- For AJV: compile JSON Schemas once; validate per response.
|
||
**Work items**
|
||
|
||
- Remove legacy UI imports (v4 → v5), delete `ts-nocheck` sections by adding minimal types.
|
||
- Replace `any` in reducers/selectors and editor props with discriminated unions & precise payloads.
|
||
- Normalize empty/loading/error states for all remote views.
|
||
**Tests**
|
||
- Contract tests: MSW returns both **valid** and **invalid** payloads → validation errors surface as user-friendly messages.
|
||
- Reducer tests: transitions for each thunk/query state.
|
||
**Validation**
|
||
- No suppressed type regions; “known issues” list empty; core routes never hard-crash on bad data.
|
||
|
||
---
|
||
|
||
## Phase 4 — Accessibility Baseline
|
||
|
||
**Libraries**
|
||
|
||
- A11y testing: **jest-axe** or **axe-core** integration; **testing-library** queries by role/name.
|
||
- Focus management: rely on MUI’s **Dialog/Popover** focus traps; add **focus-trap** only where needed.
|
||
**Contracts / behaviors**
|
||
- **Landmarks**: header/nav/main/footer; **Skip link** available and visible on focus.
|
||
- **Focus policy**: focus moves to first interactive element on open; returns to invoker on close.
|
||
- **Names/roles/states**: toggles expose `aria-pressed`; inputs have programmatic labels; errors are linked with `aria-describedby`.
|
||
- **Live regions**: polite updates for “saving…/uploaded/failed”.
|
||
- **Motion/contrast**: respect `prefers-reduced-motion`; enforce contrast tokens.
|
||
**Tests**
|
||
- Keyboard paths for protected journeys (Tab/Shift+Tab order, Escape close).
|
||
- axe checks for key pages (no critical violations).
|
||
- Live region announces long-running operations (assertions via `toHaveAccessibleDescription` or role queries).
|
||
**Validation**
|
||
- Keyboard-only success across protected journeys; automated checks pass with zero criticals.
|
||
|
||
---
|
||
|
||
## Phase 5 — UX & IA Touch-up
|
||
|
||
**Libraries**
|
||
|
||
- Router (current choice): ensure route guards & prefetch hooks supported.
|
||
- Toasts/banners: lightweight (e.g., notistack or MUI Snackbar pattern).
|
||
**Contracts / functions**
|
||
- **Design tokens**: spacing, radius, typography, color roles (text, surface, brand) centralized.
|
||
- **Common UI patterns**:
|
||
|
||
- Empty state contract: `{ icon?, title, body, primaryAction?, secondaryAction? }`
|
||
- Error surface: banner + inline guidance; all errors have recovery action.
|
||
|
||
- **Editor ergonomics**:
|
||
|
||
- Toolbar state machine: `computeToolbarState({ selection, schema }) → { boldEnabled, … }`
|
||
- Draft lifecycle: `autosaveDraft(postId, content) → { savedAt }` with debounce and “last saved” indicator.
|
||
**Tests**
|
||
|
||
- Token snapshots for theme roles; editor toolbar enable/disable based on selection.
|
||
- Error/empty states render consistently across pages (table-driven tests).
|
||
**Validation**
|
||
- Navigation clarity (first-click discoverability); zero dead ends; consistent affordances.
|
||
|
||
---
|
||
|
||
## Phase 6 — Multiple Blogs per Name
|
||
|
||
**Libraries**
|
||
|
||
- State & data: continue **RTK Query** (cache per blog key).
|
||
- Validation: same as Phase 3 (Zod/AJV).
|
||
**Domain model**
|
||
- **Name** (account) 1..N **Blog**; **Post** 1..1 **Blog** (immutable relationship post-creation).
|
||
- **Identifiers**:
|
||
|
||
- Blog handle: lowercase, slug rules; unique per **Name**; stored canonical form.
|
||
- URL composition: `/{name}/{blogHandle}/…` (conceptual, router-agnostic).
|
||
**Functions / contracts**
|
||
|
||
- `listBlogs(name): Promise<Blog[]>`
|
||
- `createBlog(input: { handle; title; visibility }): Promise<Blog>`
|
||
- `switchBlog(handle): void` (updates app-scoped “current blog”)
|
||
- `listPosts(blogId, filters)`, `createPost(blogId, input)`
|
||
- **Migration**: `backfillDefaultBlogs(): MigrationResult` (idempotent: skip if blog exists)
|
||
**Tests**
|
||
- Migration test with legacy dataset → posts appear under default blog.
|
||
- Router/selector scoping: when switching blogs, lists & counts update without leaking data.
|
||
- Handle validation: rejects collisions & invalid slugs; normalizes input.
|
||
**Validation**
|
||
- Users can create/switch blogs; all lists/summaries are scoped; legacy users retained seamlessly.
|
||
|
||
---
|
||
|
||
## Phase 7 — Shared Blogs (Collaboration)
|
||
|
||
**Libraries**
|
||
|
||
- Role/permission helpers: in-house constants; optional **casl** if we want DSL-like permissions (not required).
|
||
**Role model**
|
||
- **Owner**, **Editor**, **Author**, **Viewer** (implicit).
|
||
- Operation matrix:
|
||
|
||
- Owner: all
|
||
- Editor: edit any post, manage drafts
|
||
- Author: CRUD own posts
|
||
**Functions / contracts**
|
||
|
||
- Membership:
|
||
|
||
- `inviteMember(blogId, name, role): InviteToken`
|
||
- `acceptInvite(token): Membership`
|
||
- `removeMember(blogId, name): void`
|
||
- `listMembers(blogId): Membership[]`
|
||
|
||
- Authorization guard:
|
||
|
||
- `can(op: 'create'|'edit'|'publish'|'manageMembers', ctx: { user, blog, post? }): boolean`
|
||
|
||
- Attribution:
|
||
|
||
- Post metadata: `{ createdBy, updatedBy, updatedAt, revision }`
|
||
|
||
- Concurrency:
|
||
|
||
- `savePost(postId, input, { ifMatchRevision }): { revision }` → 409 on stale; client shows resolve UI.
|
||
**Tests**
|
||
|
||
- Permission matrix table tests (unit): each role × operation result.
|
||
- API guard tests via MSW: simulate forbidden responses, assert UI disables and surfaces denial.
|
||
- Concurrency tests: stale write attempt returns conflict → user offered resolve path.
|
||
**Validation**
|
||
- Owner can manage access; editors/authors can work within role; no unauthorized writes pass.
|
||
|
||
---
|
||
|
||
## Milestone Updates
|
||
|
||
- v0.1.x — Multiblog foundations and quality baselines delivered.
|
||
- v0.2.0 — Wiki Mode canonical selection shipped (owner/whitelist/blacklist; canonical dedupe across Names in feed, favorites, subscriptions, and post view). Settings cache added for efficient owner/settings resolution.
|
||
|
||
## Phase 8 — Performance & Resilience
|
||
|
||
**Libraries**
|
||
|
||
- **react-virtuoso** or equivalent (already used) for long lists.
|
||
- Data layer retries/backoff: **RTK Query**’s retry plugin or custom wrapper.
|
||
- RUM metrics: **web-vitals** + lightweight sender.
|
||
**Functions / policies**
|
||
- **Optimistic updates** where safe (draft save, title edits) with rollback on failure.
|
||
- **Retry policy**: GETs exponential backoff; POST/PUT guarded by idempotency keys where applicable.
|
||
- **Abortable fetch**: timeouts and user-initiated cancel for uploads.
|
||
- **Prefetching**: on-hover/visible prefetch of likely next routes and data keys.
|
||
**Tests**
|
||
- Latency injection with MSW to verify spinners/skeletons and cancel/abort behaviors.
|
||
- Optimistic update rollback test: forced server failure restores previous UI state.
|
||
**Validation**
|
||
- Subjective feel: interactions seem instant; objective: Web Vitals within budget; error paths recover without data loss.
|
||
|
||
---
|
||
|
||
## Phase 9 — Internationalization & Theming Consistency
|
||
|
||
**Libraries**
|
||
|
||
- **react-i18next** (already used in sister projects).
|
||
- Intl APIs for date/number/plural; fallback polyfills if needed.
|
||
**Functions / contracts**
|
||
- `t('namespace:key', { vars })` usage with explicit, descriptive keys.
|
||
- Currency/number/date formatters as wrapper utilities to standardize formatting.
|
||
- Theme tokens as a single source of truth; contrast checks (scripted) against WCAG AA.
|
||
**Tests**
|
||
- Snapshot tests for key screens in both light/dark with token diffs.
|
||
- Formatting tests for pluralization and number/date locales.
|
||
**Validation**
|
||
- Strings externalized; theme passes automated contrast check; RTL-safety spot check on core screens.
|
||
|
||
---
|
||
|
||
## Phase 10 — Observability, Security & Release Readiness
|
||
|
||
**Libraries**
|
||
|
||
- Error tracking: **Sentry** (or equivalent) with source maps.
|
||
- Sanitization: **DOMPurify** for any HTML serialization/render of editor output.
|
||
- CSP guidance (server-side; document the policy).
|
||
**Functions / policies**
|
||
- **Error boundaries**: route-level and editor-level with friendly fallback and “copy details”.
|
||
- **Client logging**: `logClientError({ code, message, context })` throttle; opt-in breadcrumbs (sanitized, no PII).
|
||
- **Security checks**:
|
||
|
||
- Validate all inputs client-side with Zod/AJV before sending.
|
||
- Sanitize rich text on ingest and display; allowlist for marks/nodes.
|
||
- Permission checks every write path; never trust client-side hide/disable alone.
|
||
|
||
- **Migrations**:
|
||
|
||
- Versioned, idempotent; dry-run flag; record migration status.
|
||
|
||
- **Release discipline**:
|
||
|
||
- Version bump + changelog; migration notes; upgrade guide; go/no-go checklist (green gates + budgets met).
|
||
**Tests**
|
||
|
||
- Error boundary renders fallback on thrown child component; telemetry is called with redacted payload.
|
||
- XSS tests: paste malicious payload → sanitized output (no script execution) both save and render paths.
|
||
- Migration dry-run/effects tests with sample datasets.
|
||
**Validation**
|
||
- Crashes captured with actionable context; no stored XSS vectors; release checklist green.
|
||
|
||
---
|
||
|
||
## Cross-Cutting Standards
|
||
|
||
**Contracts-first**
|
||
|
||
- Pick **Zod** _(form/input + client response validation, type inference)_ or **JSON Schema + AJV** _(cross-project parity with Q-Chess)_.
|
||
|
||
- If Zod: generate JSON Schema for docs via `zod-to-json-schema`.
|
||
- If AJV: generate TypeScript types via `json-schema-to-ts`.
|
||
|
||
- Maintain a single **roles/status/constants** module imported across UI, data, and tests.
|
||
|
||
**Data access layer**
|
||
|
||
- Prefer **RTK Query** for cache, memoized selectors, retries, and invalidation; treat queries/mutations as the one path to remote state.
|
||
|
||
**State shaping**
|
||
|
||
- Store UI state distinct from server cache; selectors derive view models (small pure functions, unit-tested).
|
||
|
||
**Security posture**
|
||
|
||
- Default-deny in permission checks; treat editor input as untrusted; output sanitize on every render path.
|
||
|
||
**Testing pyramid (target distributions)**
|
||
|
||
- Unit (\~60%): pure utils, selectors, reducers, permission guards.
|
||
- Component (\~30%): screens/components with RTL (+ axe).
|
||
- Integration/E2E (\~10%): happy-path create/edit/publish; blog switch; invite/accept.
|
||
|
||
**CI**
|
||
|
||
- Node LTS matrix (current + previous).
|
||
- Caching for package manager.
|
||
- Artifacts: coverage report, axe report, Web Vitals synthetic (if applicable), build output for preview.
|
||
|
||
---
|
||
|
||
## Example Function Signatures (illustrative, no filenames)
|
||
|
||
```ts
|
||
// Networking & validation
|
||
type Result<T> =
|
||
| { ok: true; data: T }
|
||
| { ok: false; error: { code: string; message: string; recoverable: boolean } };
|
||
function fetchJson<T>(
|
||
input: RequestInfo,
|
||
init?: RequestInit & { timeoutMs?: number },
|
||
): Promise<Result<T>>;
|
||
|
||
// Permissions
|
||
type Role = 'owner' | 'editor' | 'author';
|
||
function can(
|
||
op: 'create' | 'edit' | 'publish' | 'manageMembers',
|
||
ctx: { role: Role; userId: string; postOwnerId?: string },
|
||
): boolean;
|
||
|
||
// Blog management
|
||
function createBlog(input: {
|
||
handle: string;
|
||
title: string;
|
||
visibility: 'public' | 'private';
|
||
}): Promise<Result<Blog>>;
|
||
function inviteMember(blogId: string, name: string, role: Role): Promise<Result<{ token: string }>>;
|
||
function savePost(
|
||
postId: string,
|
||
input: PostInput,
|
||
opts: { ifMatchRevision: number },
|
||
): Promise<Result<{ revision: number }>>;
|
||
```
|
||
|
||
---
|
||
|
||
## Completion Bars (what “done” looks like per category)
|
||
|
||
- **Correctness:** zero suppressed type regions; known issues = empty; core flows never hard-crash.
|
||
- **Testability:** green typecheck/lint/tests on clean checkout; coverage trend up and stable; fast local watch.
|
||
- **Accessibility:** keyboard-only paths pass; axe shows no criticals; live region & focus rules verified.
|
||
- **Usability:** consistent patterns for empty/error; editor ergonomics predictable; no dead ends.
|
||
- **Security:** sanitized rich text; permission matrix enforced server- and client-side; no privilege escalation paths.
|
||
- **Performance:** web vitals within budget; optimistic updates/abortable fetches; graceful failure/retry.
|
||
|
||
---
|
||
|
||
**Assumptions & Risks:**
|
||
We can evolve API contracts to support multi-blog and roles; legacy data maps cleanly to default blogs; Slate output can be sanitized without losing required formatting; e2e scope remains minimal to avoid CI flake—expand only if signals require it.
|