Files
OwnCord/docs/plans/developer-experience-layout-refactor-2026-08-29.md
T
J3vbandClaude Fable 5 2a21a22ecb docs(plans): bind the layout-refactor supplement into B3/B7/B9 and record B2-2 done (#1439)
Adds docs/plans/developer-experience-layout-refactor-2026-08-29.md and wires
it into the roadmap as dated workstream lines (B3 #17, B7 #16, B9 #11) rather
than a new phase. Updates the current implementation slice and README rows
for B2-2 (PR #1438, B2-3/B2-4 folded in; B2-5 next) and fixes the new plan's
stale pending-merge header.


Claude-Session: https://claude.ai/code/session_01Rg9QQWVN3E5UUgBD2dydtu

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-29 05:51:24 +00:00

28 KiB

Developer-experience and component-layout refactor plan

Status: Draft, scheduled; not started as of 2026-08-29
Measured against: 068b5ff6 (feat/b2-2-protocol-epoch)
Current sequencing: B2-2 merged to dev 2026-08-29 (PR #1438 = 9c9b8be6, B2-3 and B2-4 folded in); B2-5 is next; no work in this plan starts before HP-2
Phase ownership: server work executes in B3 (roadmap workstream 17); client platform and layout work executes in B7 (workstream 16), with the CSS source split in B9 (workstream 11)
Authority: this is an implementation supplement. The repository-health roadmap remains the authority for phase order, entry gates, hold points, and release status.

Decision

OwnCord should refactor its internal server and client responsibilities, but it should not perform another top-level repository reorganization.

The B1 repository-foundation work already resolved the broad layout and onboarding problems: the client is flattened under Client/, cross-component protocol ownership lives under protocol/, active documentation is indexed, and root commands expose bootstrap, scoped checks, generation, formatting, and release preflight. Keep the current top-level Client/, Server/, protocol/, deploy/, docs/, scripts/, and tools/ boundaries.

The remaining friction is inside the two application components:

  • server transports, application lifecycle, persistence, and domain decisions are coupled across large packages and files;
  • the client has outgrown the broad lib/components/pages/stores split and lacks the already-designed desktop/browser platform seam;
  • high-change code and its unit tests are often far apart;
  • several files own multiple unrelated reasons to change.

This plan uses incremental responsibility extraction, dependency rules, and vertical slices. It explicitly rejects a wholesale rewrite, arbitrary line-count splitting, and a big-bang feature-folder migration.

Outcomes

The refactor succeeds when a new contributor can:

  1. find the correct server or client responsibility without tracing unrelated orchestration code;
  2. identify the owning tests next to, or clearly indexed from, the code;
  3. run a focused local check and the correct stack-level gate without guessing directories;
  4. add a REST endpoint, WebSocket behavior, client feature, or native capability through one documented dependency path;
  5. understand which files are generated and which command regenerates them;
  6. make changes without accidentally coupling domain behavior to HTTP, WebSocket, database, Tauri, or browser implementation details.

Non-goals

  • Rename Client/ or Server/ for casing or style.
  • Introduce a new monorepo framework or require Node for Go-only work.
  • Move all server packages under internal/ for appearance alone.
  • Replace the existing Go service and storage layers wholesale.
  • Fork desktop and browser into separate applications.
  • Move all existing client unit tests in one mechanical migration.
  • Change release asset names, updater contracts, protocol behavior, or generated file ownership as a side effect of layout work.
  • Mix product features, security fixes, file moves, and architecture extraction in the same change.

Current-state evidence

Repository foundations to preserve

  • Root package.json exposes bootstrap, full and scoped checks, formatting, generation, and release preflight through scripts/run.mjs.
  • CONTRIBUTING.md, docs/contributing.md, docs/README.md, and the component CLAUDE.md files provide discoverable contributor entry points.
  • Server/CLAUDE.md documents the current Go package responsibilities and the required default, otel, wazero, race, and deadlock variants.
  • Client/CLAUDE.md documents the single WebSocket-to-store dispatch invariant, Tauri constraints, test tiers, and generated-code boundaries.
  • protocol/schema.json owns generated Go and TypeScript protocol constants.
  • Client Vitest already includes both tests/**/*.test.ts and colocated src/**/*.test.ts, so gradual test colocation needs no new runner.

Server pressure points

Tracked production Go files currently cluster as follows:

Package Production files Approximate production lines Observation
ws 45 11,700 Hub, connection, replay, broadcast, voice, persistence, and permission behavior share one package.
db 32 6,700 Hand-written persistence and generated sqlc ownership are correctly separated, but many upper layers still import it directly.
api 20 6,400 HTTP handlers still own persistence and domain decisions in several families.
admin 19 4,300 Admin transport/UI handling frequently reaches directly into persistence.
service 18 4,200 A useful domain seam exists, but it does not yet own all use cases.

Additional evidence:

  • 44 production files across ws, admin, and api directly import db: 17, 16, and 11 respectively.
  • Server/main.go exceeds 1,000 physical lines and owns configuration, data preparation, database initialization, telemetry, plugins, event persistence, auditing, maintenance, HTTP serving, shutdown, health checks, and replay seeding.
  • ws/hub_broadcast.go, ws/serve.go, and ws/hub.go are large coordination hotspots where locking, ordering, replay, visibility, and lifecycle behavior intersect.
  • The existing issue register already assigns direct database ownership, lifecycle/hub extraction, constructor completeness, and canonical permission predicates to B3 (S-08 through S-12).

Client pressure points

Tracked TypeScript and CSS currently cluster as follows:

Area Files Approximate lines Observation
src/components 60 18,400 Shared UI, feature UI, orchestration, and native-facing behavior are mixed.
src/lib 63 15,400 The name has become a catch-all for transport, voice, E2EE, profiles, native APIs, validation, and application orchestration.
src/styles 5 7,400 app.css alone exceeds 5,600 physical lines and contains many feature-specific sections.
src/pages 19 5,900 Page files and partial page subfolders show useful decomposition already in progress.
src/stores 9 2,900 Stores are broadly imported by components, pages, and library modules.

Additional evidence:

  • Production source contains approximately 405 @lib/*, 146 @stores/*, and 88 @components/* import references.
  • 20 production frontend files import @tauri-apps/*; the planned src/platform/ seam does not exist yet.
  • Major responsibility hotspots include livekitSession.ts, livekitE2EE.ts, dispatcher.ts, MessageList.ts, AccountTab.ts, MainPage.ts, and app.css.
  • 190 ordinary unit-test files live under tests/unit. This makes test tiers obvious, but it often separates a module from the fastest explanation of its behavior.
  • Existing findings C-11 and C-12 already assign import-cycle removal and cohesive hotspot extraction to B7/B9.

Architecture standards

General change discipline

  1. Separate pure moves, path rewrites, dependency changes, behavior changes, and generated output into reviewable commits.
  2. Extract by responsibility and dependency direction, not by a maximum line count.
  3. Introduce and test a seam before moving callers through it.
  4. Migrate one server domain family or one client platform capability per pull request.
  5. Preserve behavior with characterization, contract, race, deadlock, mutation, browser, and end-to-end tests as appropriate to the boundary.
  6. Add a static rule for a boundary before relying on documentation alone.
  7. Prefer explicit names over generic utils, common, helpers, manager, or repository-wide interfaces packages.
  8. Define narrow interfaces beside their consumers. Do not build one shared interface catalog.
  9. Keep generated sources committed and generated from their documented source of truth; never hand-edit generated consumers.

Server dependency direction

Target dependency flow:

main / internal/app
        |
        v
api / admin / ws transports
        |
        v
service use cases and consumer-owned interfaces
        |
        v
db and other infrastructure adapters

Rules:

  • api, admin, and WebSocket message handlers translate transport input and output; they do not decide reusable domain policy.
  • Services own application use cases, authorization orchestration, transaction intent, and stable domain error mapping.
  • Persistence packages implement narrow service-owned needs. Direct database access above services must be classified as an explicit transaction, composition, migration, or transport-adapter exception.
  • main.go remains the compatible go build . entry point but becomes a thin composition root.
  • internal/app/ may own construction, start, drain, stop, maintenance, and composite cleanup because those responsibilities are application-private.
  • Keep ws as one package while its locking invariants require shared private state. First split responsibilities into clearly named files; create subpackages only when doing so does not force exports or introduce cycles.

Client dependency direction

Target dependency flow:

app -> features -> shared
 |         |
 +---------+----> platform/contracts
                         ^
                         |
              platform/desktop or browser

Rules:

  • app/ owns bootstrap, composition, navigation, and the single point that registers domain dispatcher modules.
  • features/ owns cohesive user capabilities such as connection, messaging, channels, direct messages, voice, and settings.
  • shared/ contains pure reusable UI, DOM, validation, formatting, and token code. It cannot import feature state or platform implementations.
  • platform/contracts/ contains asynchronous, host-neutral interfaces only.
  • platform/desktop/ is the only ordinary production location allowed to import Tauri APIs.
  • platform/browser/ implements browser behavior or returns an explicit unsupported result. It must not imitate certificate pinning, OS keychain protection, or global push-to-talk.
  • Features may expose narrow public entry points. They may not import another feature's internal modules.
  • lib/ and stores/ are transitional boundaries. Shrink them as ownership is proven; do not bulk-move them merely to make the target tree appear complete.

Directional target layout

This tree describes responsibility ownership. It is not a single move list.

Server/
  main.go                     # thin executable and composition entry
  internal/
    app/                      # construction, lifecycle, maintenance, cleanup
  api/                        # HTTP transport adapters
  admin/                      # admin transport and server-owned admin UI
  ws/                         # WebSocket transport and connection mechanics
  service/                    # domain use cases and narrow interfaces
  db/                         # persistence adapters
    dbgen/                    # generated sqlc output
    queries/                  # sqlc source queries
  cmd/                        # standalone operator/developer tools

Client/src/
  app/                        # bootstrap, composition, routing, dispatch root
  platform/
    contracts/               # host-neutral asynchronous interfaces
    desktop/                 # Tauri implementations
    browser/                 # browser implementations or explicit refusals
  features/
    connection/
    messaging/
    channels/
    direct-messages/
    voice/
    settings/
  shared/
    ui/
    dom/
    validation/
    styles/
  lib/                        # transitional; retain generated protocol path
  stores/                     # transitional global state

Execution plan

Phase 0 — Complete B2 and freeze the contracts

Timing: now; this is prerequisite work, not layout work.

  1. Merge B2-2 to dev as its own completed protocol-epoch slice.
  2. Continue the serialized B2-3 and B2-4 work and finish the remaining B2 workstreams according to the active B2 plan.
  3. Accept HP-2 only after protocol, trust, permission, E2EE, updater, and compatibility evidence is green on one exact dev SHA.
  4. Do not mix any B3/B7 structural move into a B2 change.

Exit gate: HP-2 accepted; protocol fixtures and compatibility behavior are stable enough to guard later extraction.

Phase 1 — Record boundary inventories and automated rules

Timing: B3 entry preparation after HP-2.

  1. Inventory every production database import in api, admin, auth, ws, root composition, and plugin code.
  2. Assign each call one disposition: move behind a service, retain as an explicit adapter, retain as a transaction/composition boundary, or remove.
  3. Inventory hub construction setters, lifecycle collaborators, lock ownership, start/stop ordering, and failure paths.
  4. Record before-state dependency graphs for the first server vertical slice.
  5. Refresh the client native-import, Rust-command, import-cycle, test-warning, timer/listener, bundle, coverage, and mutation baselines before B7.
  6. Add the narrowest mechanical dependency checks supported by the evidence.

Exit gate: every crossing in scope has an owner and disposition; the checks can detect a newly introduced violation without false positives.

Phase 2 — Execute the first server vertical slice

Timing: B3, before repeating the pattern elsewhere.

Use authentication as the first slice because S-10 already identifies raw database ownership in auth routes as the intended pilot.

  1. Characterize authentication, enumeration defense, session, TOTP, sentinel error, and failure behavior before moving dependencies.
  2. Define a narrow auth use-case interface beside the HTTP consumer.
  3. Move authentication orchestration behind the service boundary.
  4. Keep persistence details in db; preserve error semantics at the service boundary.
  5. Retain thin request decoding, response encoding, and middleware adaptation in api.
  6. Record the before/after dependency graph and exact checks.

Hold point: HP-3 reviews the complete route-to-service-to-storage slice. Repeat the pattern only if it reduces coupling without weakening B2 contracts.

Phase 3 — Extract server lifecycle and hotspot responsibilities

Timing: B3 after HP-3 accepts the first slice.

  1. Keep main.go as the executable entry but move application-private wiring into internal/app/:
    • configuration and data preparation;
    • construction and validated dependencies;
    • background workers and maintenance;
    • server start, drain, stop, and composite cleanup;
    • failure and restart coordination.
  2. Replace required post-construction hub setters with validated constructor options. Leave setters only for genuinely replaceable runtime state.
  3. Split WebSocket responsibilities inside the existing package before considering subpackages:
    • handshake authentication;
    • fresh-connect initialization;
    • replay selection and delivery;
    • registry and supersession;
    • visibility and permission refresh;
    • broadcast delivery and backpressure;
    • voice session and moderation lifecycle.
  4. Replace mirrored permission and visibility decisions with canonical value-taking predicates and parity tests.
  5. Move one remaining domain family at a time behind services, with explicit transaction exceptions where necessary.

Exit gate: lifecycle ownership is explicit; required construction cannot be incomplete; every upper-layer database access is removed or justified; race, deadlock, replay, reconnect, failure-injection, coverage, and benchmark gates remain green.

Phase 4 — Establish the client platform seam

Timing: B7, after the complete beta server is stable through B6. Do not pull this work forward merely because the target directories are documented.

  1. Re-run the client mutation baseline before decomposition.
  2. Introduce typed asynchronous contracts for HTTP, WebSocket, credentials, settings/profiles, notifications, media, LiveKit proxies, logs/filesystem, window state, updater/process, opener, file selection, PTT, deep links, and application metadata.
  3. Add desktop implementations and move the 20 native import sites behind them one responsibility at a time.
  4. Add contract tests before creating browser implementations.
  5. Add a static rule that rejects Tauri imports outside approved desktop and bootstrap ownership.
  6. Split shared Vite configuration from target-specific behavior and add explicit build:desktop and build:web gates from one application tree.
  7. Delete dead Rust command surfaces identified by the platform-contract map.

Hold point: HP-7 proves desktop parity before browser implementations are filled in.

Phase 5 — Decompose client features and hotspots

Timing: B7 for architecture and desktop parity; B9 for later feature UX and style rationalization.

Use the target layout for new or extracted code, not as a reason to bulk-move every existing file.

  1. dispatcher.ts: retain one composition entry, but register messaging, presence, channel, DM, voice, and compatibility handlers from cohesive modules. Preserve the rule that WebSocket-driven store writes enter through dispatcher ownership.
  2. livekitSession.ts: separate session attempt ownership, room lifecycle, device/media control, remote tracks, and debug/statistics behavior without weakening supersession rules.
  3. livekitE2EE.ts: separate identity/key management, epoch transitions, worker lifecycle, participant state, and error reporting along existing staleness guards.
  4. Messaging: separate virtual-list state, loading/windowing, renderers, attachments/media/embeds, reactions, and row lifecycle.
  5. Settings: split account profile, avatar, password, TOTP, status, deletion, notifications, appearance, accessibility, voice, logs, and advanced/native capabilities into owned feature sections.
  6. CSS: preserve selectors and built output while moving source sections into tokens/base, shared shell, messaging, sidebar, voice/video, settings, overlays, and responsive/accessibility files. Do not mix visual redesign into the source split.
  7. Remove production import cycles or document the smallest unavoidable seam with a boundary test.
  8. Centralize timer, listener, and teardown ownership for long-lived features.

Exit gate: client dependency rules pass; desktop behavior is unchanged; import cycles and unexplained warnings are gone or explicitly accepted; coverage, mutation, teardown, bundle, browser, and E2E evidence does not regress.

Phase 6 — Improve test proximity without losing test tiers

  1. Colocate unit tests with every newly created or materially extracted source module using src/**/*.test.ts.
  2. Move an existing unit test only when its owning production module is already being extracted and the move remains reviewable.
  3. Keep cross-component contracts under tests/contract, multi-module integration under tests/integration, real-browser tests under tests/browser, and Playwright under tests/e2e.
  4. Document any fixture shared across tiers and give it one owner.
  5. Do not weaken assertions to make a mechanical move pass.

Exit gate: a developer opening a moved or new module can find its focused tests immediately; stack-level test commands and coverage still include every tier intended by CI.

Phase 7 — Finish the contributor path

  1. Add a concise change map to active contributor or architecture guidance:
    • add or change a REST endpoint;
    • add or change a WebSocket event;
    • change the generated protocol;
    • add a server domain operation;
    • add a client feature or setting;
    • add a desktop or browser capability;
    • add a migration or query;
    • choose the correct test tier.
  2. Add root shortcuts only for measured high-frequency development paths. Keep direct Go commands first-class and Node-free for server-only contributors.
  3. Print scoped commands and working directories, as scripts/run.mjs already does for verification.
  4. Keep architectural ownership in one maintained map rather than adding a README to every directory.
  5. Exercise the guide with one fresh-contributor scenario and record every point where the contributor must guess.

Exit gate: a fresh Windows or Linux contributor can bootstrap the relevant stack, locate a common change path, run a focused test, and run the appropriate full stack check without undocumented knowledge.

Phase 8 — Exact-SHA structural qualification

Run the complete relevant matrix on the exact resulting SHA:

  • all four Go build-tag variants;
  • Go vet, race, deadlock, generated protocol, generated database, and lint;
  • client typecheck, lint, format, unit, integration, contract, browser, and E2E;
  • Rust format, tests, and Clippy;
  • desktop and web production builds when B7 owns both;
  • Docker and release-path checks required by the owning phase;
  • generated and documentation drift checks;
  • dependency-boundary and import-cycle checks;
  • clean process termination and clean worktree verification.

Exit gate: every artifact and report points to the same commit; there is no accidental generated or build output; file moves are behavior-neutral; accepted exceptions have an owner, rationale, and review trigger.

Pull-request and commit strategy

Each migration unit should normally use this order:

  1. add or tighten characterization/contract tests;
  2. add the new boundary and its dependency rule;
  3. move one responsibility without changing behavior;
  4. mechanically rewrite imports and paths;
  5. remove the old path after all consumers move;
  6. run scoped checks, then the phase-required full matrix;
  7. update the boundary inventory and architecture/change map.

Pure moves and mechanical path rewrites should be adjacent commits. Functional changes discovered during extraction belong in separate pull requests unless a failing safety test proves they are inseparable.

Measures and acceptance criteria

Server

  • Every production database import above persistence/service ownership is removed or recorded as an explicit allowed boundary.
  • Authentication completes one reviewed vertical slice before the pattern is copied.
  • Application construction, start, drain, stop, restart, and cleanup have explicit owners and failure tests.
  • Required hub collaborators are validated at construction.
  • Permission and visibility security properties have one production predicate per property.
  • No extraction regresses race, deadlock, replay, reconnect, coverage, or benchmark evidence.

Client

  • Ordinary production Tauri imports exist only under approved desktop adapter and bootstrap ownership.
  • Desktop and web targets compile from the same application source.
  • Platform contracts are asynchronous and make unsupported capabilities explicit.
  • Production dependencies are acyclic, or the smallest approved exception has a boundary test and rationale.
  • New and extracted unit-test ownership is colocated; higher test tiers remain centralized.
  • Hotspot decomposition preserves dispatcher, voice supersession, E2EE staleness, teardown, and generated-protocol invariants.
  • CSS source ownership is feature-oriented without visual behavior changes in the structural migration.

Contributor experience

  • Root and per-stack commands remain discoverable and cross-platform.
  • Server-only development remains possible without Node.
  • A maintained change map answers where common additions belong and which tests prove them.
  • Generated source ownership is visible from every relevant entry point.
  • A fresh-contributor exercise completes without undocumented directory or command discovery.

Risks and controls

Risk Control
A large move hides behavior changes Separate characterization, move, path rewrite, and behavior commits; restart structural review when a move commit changes logic.
Go subpackages force exports or cycles Split files inside the owning package first; create a subpackage only after dependency direction is proven.
Service interfaces become generic abstractions Define the smallest interface beside its consumer and implement one real use case before repeating it.
Desktop behavior changes during adapter extraction Add contract tests first and reach HP-7 desktop parity before filling browser implementations.
Browser adapters weaken security claims Return explicit unsupported states for certificate pinning, OS-keychain equivalence, and global PTT.
Feature folders become a cosmetic bulk migration Use the target only for new/extracted responsibilities; leave transitional lib and stores until ownership is proven.
Test moves reduce coverage or confuse tiers Move unit tests only with their production modules; keep contract/integration/browser/E2E tiers centralized.
CSS splitting becomes a redesign Preserve selectors and rendered output; perform visual changes separately with visual/accessibility evidence.
Root tooling makes Node mandatory Keep direct Go commands documented and supported; root npm remains an optional facade for server-only work.
Generated output is edited during moves Preserve the source-of-truth workflows and run drift checks after every affected migration.

First actionable slice

After B2-2 merges and HP-2 is accepted:

  1. write the B3 database-call and lifecycle inventory;
  2. record the before-state dependency graph;
  3. add characterization tests for auth enumeration and error mappings;
  4. execute the authentication route-to-service-to-storage vertical slice;
  5. review it at HP-3 before extracting any other server family.

Do not create Client/src/platform/ or begin client feature moves during this slice. The client implementation work remains B7; the existing platform map is the design input until its entry gate is met.