Files
hemx/PLAN.md
T
slhx agent 80f1ae155e feat(kanban): bound sync upload runs
req: sync/017
2026-07-13 17:48:47 +02:00

17 KiB

Hemx v1 implementation plan

Authority: REQUIREMENTS.md. Product evidence: docs/v1-product-evidence.md. This file is an execution cursor, not requirement authority or release permission. Publishing, deployment, artifact upload, and package-registry mutation are out of scope.

Product boundary

Hemx v1 keeps server-rendered HTML and ordinary Rust as the default. Generated resources and the versioned EffectBatch are the one UI contract. Client-local WASM and durable sync are optional execution layers, not a component framework. Hemplate remains the rendering/Surface foundation; application auth, persistence, encryption, retention, backup, and deployment policy remain host concerns.

Slice 1 — one real client-local handler

  • User value: a Rust author marks one high-frequency handler local and gets immediate browser behavior without app-authored JavaScript or a request.
  • State: Done. hemx-build validates same-origin client module metadata and generates the WASM import, initialization, handler registration, fingerprint, and ready marker. Client handlers receive versioned ClientEvent/root-owned ClientState; incompatible input is rejected before handler execution, reports an actionable hemx:client-error, restores pending UI, and invokes an explicitly declared server fallback. Real WASM applies the ordinary generated-target EffectBatch with zero request on valid input.
  • Build: add the smallest optional hemx-wasm boundary for #[hemx::handler(client)]; export only opted-in handlers; generate typed event/state ABI glue; run one existing generated-target interaction through the ordinary EffectBatch interpreter; preserve an explicit native/server fallback.
  • Refusals: no VDOM, component lifecycle, global store, sync queue, second effect protocol, or generic WASM framework.
  • Requirements: client_local/001-010, security/001, security/005-006, performance/003, v1_release/001.
  • Proof: cargo test -p hemx-wasm --test browser client_handler_applies_effect_batch_without_network -- --exact serves only generated template HTML, the ordinary runtime, and generated client bootstrap; it visibly updates a generated target through real WASM, keeps the resource count unchanged for valid input, and proves invalid state diagnostics, pending restoration, and one declared fallback request. Formatting, workspace tests, strict all-target Clippy, and wasm-target build pass.

Slice 2 — direct manipulation that survives interruption

  • User value: Kanban drag/reorder follows the pointer immediately, remains keyboard operable, and cannot apply stale work after cancellation or root removal.
  • State: Done. The canonical Kanban client board is rendered through Hemplate and generated resources, then reordered by its real WASM handler for drag/drop and Arrow-key interaction. Client-local runs use validated latest/drop policy; stale/unmounted completions cannot apply effects; moved-card focus, live status, reduced-motion state, root cleanup, and a measured sub-100 ms local response are browser-proven.
  • Build: use the client handler in the canonical Kanban path; add cancellation/supersession, root-owned state cleanup, keyboard equivalent, focus/status behavior, reduced-motion behavior, and measured response/frame budgets.
  • Refusals: no persistence, collaboration, or animation framework yet.
  • Requirements: client_local/011-014, accessibility/001-007, operations/002-003, performance/001, performance/003, milestone/001.
  • Proof: cargo test -p hemx-wasm --test browser kanban_reorder_has_pointer_keyboard_focus_and_reduced_motion_parity -- --exact covers real-WASM pointer and keyboard reorder, focus/status, reduced motion, and the 100 ms response budget; the client-handler browser proof covers cancellation, removal, error recovery, zero-request behavior, and root cleanup.

Slice 3 — durable offline command log

  • User value: an opted-in Kanban mutation remains available after network loss and browser reload without storing DOM patches as truth.
  • State: Complete — the app-owned reorder_card command is transactionally persisted with schema, actor, session, causal id, and app payload before projection; an app-owned service worker caches only the generated shell/resources, and reload restores the projection after the fixture server is stopped and proven unreachable. Native recovery controls export a versioned credential-free command envelope, delete queued commands while preserving actor/causal identity, and reset command data, identity, cache, and registration behind explicit confirmation. Unknown schemas and malformed current-schema records stop before projection with explicit recovery diagnostics. Transaction conflicts and injected quota exhaustion neither project nor emit durability claims and remain recoverable through export/delete/reset. Replay is preflighted before effects, capped by the app at 64 commands, and measured against a 100 ms browser budget. A programmatic queued/busy state appears within the 100 ms direct-interaction budget before blocked persistence completes. The ordinary server-first route loads only the fingerprinted base runtime and creates no client root, service worker, or IndexedDB database; PWA/WASM policy remains app-owned and opt-in.
  • Build: add an optional durable command-log adapter around platform transactional storage; persist versioned command ids and app payload before projection; restore projection after reload; expose queue state, export/delete/reset, quota/corruption failure, and migration refusal.
  • Refusals: no server reconciliation, CRDT, mandatory IndexedDB, credential storage, or policy hidden in core.
  • Requirements: local/001-004, sync/009, sync/014-015, sync/020, security/007, accessibility/004, client_local/013, performance/003, performance/005-006.
  • Proof: cargo test -p hemx-wasm --test browser kanban_command_persists_before_projection_and_restores_after_reload -- --exact proves transactional persist-before-project ordering, app-owned shell caching, current-version replay after a real Firefox reload with the fixture server unreachable, stable identity metadata, and explicit unknown-schema refusal through real WASM. cargo test -p hemx-wasm --test browser kanban_command_export_delete_and_reset_are_recoverable -- --exact proves accessible export/delete/reset entry points, versioned credential-free export, confirmation before destructive actions, preserved identity after queue deletion, and fresh identity/baseline projection after reset. cargo test -p hemx-wasm --test browser kanban_persistence_failure_does_not_project_and_recovers -- --exact proves transactional failure does not project or emit kanban:command-persisted, reports non-payload stage/code diagnostics, and recovers through the ordinary deletion path. cargo test -p hemx-wasm --test browser kanban_corrupt_command_refuses_projection_and_recovers -- --exact proves strict current-schema validation, no partial projection, visible non-payload diagnostics, raw versioned export for recovery, and ordinary deletion recovery. cargo test -p hemx-wasm --test browser kanban_replay_is_bounded_and_within_budget -- --exact proves 64-command preflight/replay within the 100 ms browser budget, zero partial projection at 65 commands, and export/delete recovery. cargo test -p hemx-wasm --test browser kanban_quota_failure_is_fail_closed_and_recoverable -- --exact proves quota-specific fail-closed behavior, no false durability event, transactional metadata rollback, and export/delete/reset recovery. cargo test -p hemx-wasm --test browser kanban_queued_status_precedes_durable_projection_within_budget -- --exact proves queued/busy feedback within the performance/003 and client_local/013 100 ms budget while persistence is blocked, followed by durable projection only after commit. cargo test -p hemx-kanban-example --test browser_e2e server_first_route_does_not_load_optional_client_assets -- --exact proves the server-first route loads only its fingerprinted base runtime and creates no optional client root, PWA/WASM request, service worker, or IndexedDB database.

Slice 4 — authoritative reconnect and convergence

  • User value: offline and concurrent work reconnects without duplicate mutation, silent loss, stale authorization, or ambiguous conflict.
  • State: In progress — one app-owned move_card server command validates a durable client command id, applies the authoritative canonical column once, returns the same acknowledgement for an identical retry, rejects id reuse with a different payload, assigns one server sequence, and redelivers that canonical acknowledgement after a real EventSource disconnect/reconnect. Canonical acknowledgements and the next sequence are durably stored in a strict versioned JSON envelope using fsync plus atomic replacement; startup refuses malformed/unknown state, rebuilds the canonical board, and preserves idempotency and event replay across a real process restart. A dedicated opt-in sync route reads one pending IndexedDB command, retries transient failures with capped exponential backoff and randomized jitter, exposes online/offline state plus an accessible manual retry after exhaustion, waits for canonical acknowledgement on the reconnecting transport, and only then removes the durable command; later retry converges without a new local mutation. Canonical payload conflicts are not retried and remain durable with a visible reason. If requested history predates retained events, the stream emits a typed snapshot-required event and the client loads a versioned canonical snapshot. One deterministic rebase rule treats reorder_card as converged only when the canonical snapshot already places that card in done; it then atomically stores the snapshot/cursor and removes the satisfied command. If a later canonical command instead places the same card in doing, the rebase is explicitly conflicted, retains the local command and last committed snapshot/cursor unchanged, and exposes the divergent canonical column/reason. Two same-origin tabs coordinate an app-owned expiring IndexedDB lease so only one uploads; the standby exposes its role without issuing a request, and after the leader closes it takes over, receives one canonical acknowledgement/sequence, and removes the queue once. Each activation serializes uploads with one in flight, processes at most two acknowledged commands, exposes the retained durable count when backpressured, and resumes the next bounded run only through the visible retry action. Broader conflict decisions, upgrade mid-queue, and auth isolation remain.
  • Build: materialize hemx-sync over an integration transport with idempotent server command processing, snapshot/change cursor, durable acknowledgements, bounded ordered replay, current auth checks, rejection/conflict results, canonical replacement, reconnect jitter/backoff, multi-tab coordination, and redacted diagnostics.
  • Refusals: no default CRDT, transport in core, cached enqueue-time permission, unbounded queue, or silent last-write-wins policy.
  • Requirements: sync/001-023, operations/001-005, security/002-005, performance/004-005.
  • Proof: cargo test -p hemx-kanban-example --test browser_e2e idempotent_server_command_is_acknowledged_after_reconnect -- --exact proves duplicate POST delivery yields one identical canonical acknowledgement/sequence, conflicting id reuse is rejected, EventSource reconnects after a server-closed first stream, the acknowledgement is delivered once with its sequence as event id, and a page reload shows the authoritative card in the canonical column. cargo test -p hemx-kanban-example --test browser_e2e pending_local_command_uploads_with_bounded_retry_and_is_removed_on_ack -- --exact proves automatic platform-store upload, one explicit transient failure, bounded exponential backoff plus jitter, reconnect acknowledgement, pending-before-ack ordering, acknowledged removal, canonical board convergence, and non-retried 409 rejection remaining durable with a visible reason. cargo test -p hemx-kanban-example --test browser_e2e canonical_acknowledgement_survives_server_restart -- --exact proves the versioned store is materialized before success, a real process restart reloads the same idempotent acknowledgement/sequence, EventSource replays it by id, and canonical board state is rebuilt. cargo test -p hemx-kanban-example --test browser_e2e exhausted_offline_retries_keep_command_until_later_reconnect -- --exact proves three bounded retries exhaust into visible offline/manual-recovery state while the command remains durable, then a later retry acknowledges/removes it and converges canonically. cargo test -p hemx-kanban-example --test browser_e2e missing_history_rebase_converges_without_losing_local_intent -- --exact proves retained-history gap detection, typed/versioned snapshot fallback, deterministic already-canonical convergence, atomic snapshot/cursor commit with acknowledged removal, then a divergent canonical update producing explicit conflict while the local command and prior committed snapshot/cursor remain intact. cargo test -p hemx-kanban-example --test browser_e2e two_tabs_coordinate_single_uploader_and_takeover_without_duplicate_application -- --exact proves one retry-exhausted leader/one explicit standby, zero follower upload before takeover, lease takeover after the leader closes, one canonical sequence/event, one queue removal, and one board application. cargo test -p hemx-kanban-example --test browser_e2e upload_backpressure_keeps_pending_work_visible_and_recoverable -- --exact proves one in-flight upload, a two-acknowledgement activation limit, one retained durable command with visible recovery state, and explicit retry draining the final command without loss. The completed slice proof must additionally cover broader partial reject/conflict decisions, upgrade mid-queue, and multi-user isolation.

Slice 5 — local-first multiplayer Kanban milestone

  • User value: the complete north-star app demonstrates SSR-first startup, direct manipulation, offline durability, optimistic projection, reconciliation, and live presence as one comprehensible workflow.
  • State: Blocked by Slice 4.
  • Build: connect the previous slices in the canonical Kanban example; keep native server-rendered fallback; add presence and server-canonical conflict presentation; exercise deploy fingerprint recovery and accessible online/offline/conflict state.
  • Refusals: no demo-only runtime, hidden app JS, proprietary service, or requirement to load collaboration code for server-first apps.
  • Requirements: milestone/001, v1_release/001-002, accessibility/001-007, operations/006, performance/006.
  • Proof: one browser journey covers SSR/no-script fallback, local drag, keyboard reorder, offline/reload, concurrent peer edit, reconnect/convergence, presence, conflict/rejection recovery, mixed-version reload, and optional-asset isolation.

Slice 6 — production integration reference

  • User value: adopters can copy a proven boundary for durable storage, auth, transactions, security controls, observability, and restart recovery without hemx owning vendor policy.
  • State: Ready after public execution/sync contracts stabilize.
  • Build: evolve one existing reference app using ordinary integration adapters; add durable app storage, authenticated/authorized allowed and denied mutations, CSRF/origin checks, transaction rollback, bounded input, structured failures, health/readiness, tracing/metrics hooks, and restart/deploy recovery.
  • Refusals: no built-in database/auth provider, compliance claim, telemetry vendor, deployment system, or repository framework.
  • Requirements: security/001-009, operations/001-008, v1_release/003, existing adapter/*, integration/*, and diagnostics/* contracts.
  • Proof: end-to-end test survives process restart and mixed deployment, proves allowed/denied/rolled-back mutations and redacted diagnostics, and maps each framework-owned ASVS-relevant control to a failing/passing case.

Slice 7 — v1 compatibility and closure

  • User value: maintainers and adopters receive a reproducible, migration-aware v1 with no known material contradiction and no hidden publication side effect.
  • State: Blocked by Slices 1-6 and explicit authority for any missing local audit tool installation.
  • Build: freeze the supported Rust/browser/WASM/integration matrix; reconcile public/generated/Surface/symbol/wire/runtime/persisted-schema compatibility; add migration fixtures; make canonical examples compatibility tests; update the progressive tutorial path; run all local release gates and disposition every P0/P1, advisory, unsafe-code, license, performance, accessibility, and documentation finding.
  • Refusals: no publish, deploy, upload, store submission, speculative feature, or weakening a gate to make it pass.
  • Requirements: v1_release/001-010, versioning/*, test/*, diag/*, performance/*, security/008, and all requirements changed by the preceding slices.
  • Proof: clean-tree formatting, workspace tests, strict all-target Clippy, compile-fail, browser/WASM/offline/multiplayer scenarios, benchmark budgets, approved pinned lockfile audit, requirements proof audit, docs/examples checks, and independent contradiction review all pass with no unresolved P0/P1.