15 KiB
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-corevia 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-schemafor 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
- For Zod:
-
Remove legacy UI imports (v4 → v5), delete
ts-nochecksections by adding minimal types. -
Replace
anyin 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 witharia-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
toHaveAccessibleDescriptionor 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.
- Empty state contract:
-
Editor ergonomics:
- Toolbar state machine:
computeToolbarState({ selection, schema }) → { boldEnabled, … } - Draft lifecycle:
autosaveDraft(postId, content) → { savedAt }with debounce and “last saved” indicator. Tests
- Toolbar state machine:
-
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): InviteTokenacceptInvite(token): MembershipremoveMember(blogId, name): voidlistMembers(blogId): Membership[]
-
Authorization guard:
can(op: 'create'|'edit'|'publish'|'manageMembers', ctx: { user, blog, post? }): boolean
-
Attribution:
- Post metadata:
{ createdBy, updatedBy, updatedAt, revision }
- Post metadata:
-
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.
- If Zod: generate JSON Schema for docs via
-
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)
// 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.