Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions docs/architecture-audit-2026-07-23/TeamInbox.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Architecture Audit — Team Inbox

**Scope:** Team Inbox TypeScript domain/UI/data source, managed-cloud mention RPC client, project-management SQLite projection, Tauri commands, Sidebar and Chat Panel tab integration.
**Date:** 2026-07-23

## Layer 1 — Compilation correctness

- TypeScript `tsc --noEmit`: passed.
- Tauri application `cargo check -p org2`: passed.
- Focused Rust Team Inbox tests: 7 passed.

## Layer 2 — Dead code and structural deduplication

- Production entry path is Sidebar row → singleton Team Inbox tab → connected view → shared cache/data source → local Tauri projection plus managed-cloud mention RPC.
- Sidebar badge and rendered page consume the same cache; no second unread query implementation remains.
- Local assignment reads remain in SQLite; the frontend does not rescan every project Work Item.
- Mention response mapping is centralized in the Team Inbox data source; sorting/filtering/deduplication remain pure domain selectors.

## Layer 3 — Naming consistency

- Wire `work_item_assigned` is mapped once to UI `assigned_work_item`; names are explicit at the boundary.
- `viewerMemberIds` is used consistently for the local viewer identity. The cloud RPC deliberately accepts no viewer ID because JWT identity is authoritative.
- Sidebar/menu/tab terms consistently use `team-inbox` / `Team Inbox`.

## Layer 4 — Semantic overloading

| Term | Meaning in this change | Verdict |
| ------------ | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| viewer | Explicit local project member IDs, or managed-cloud JWT subject | Kept separate at transport boundaries; never inferred from an agent/session ID. |
| read receipt | SQLite viewer-scoped receipt for local assignment; endpoint+user+org scoped persisted receipt for cloud mention | Separate storage owners with one UI read state. |
| projectId | Project slug for project-store navigation; empty for standalone Work Items | Boundary is explicit and standalone navigation uses the standalone API. |

## Layer 5 — Default branch analysis

- Item-kind branching uses discriminated unions with explicit mention/assignment cases; unsupported wire combinations throw.
- Local mentions filter returns an explicit empty page rather than falling through to assignments.
- Cloud RPC failure degrades to local items only; it does not fabricate mention data or scan comment bodies.

## Layer 6 — Cross-domain concept leakage

- Project-management owns only local assigned Work Item projection and receipt DDL.
- Managed-cloud mention transport remains under `features/Org2Cloud`.
- Presentation consumes a transport-independent Team Inbox domain contract.

## Layer 7 — New developer confusion test

- `ConnectedTeamInboxView` identifies the production-wired surface; `TeamInboxView` remains injectable for tests/reuse.
- `useTeamInboxDataSource` names local/cloud composition and identity resolution explicitly.
- `useTeamInboxNavigation` separates Session comment navigation from project/standalone Work Item navigation.

## Layer 8 — Wire protocol and serialization

- Local DTOs use serde-tagged target/payload variants and camelCase fields, covered by Rust serialization tests.
- Cloud request body contains only `p_org_id`, `p_cursor`, and `p_limit`; tests assert no caller-supplied viewer/user ID.
- Cloud response is Zod-validated; malformed counts and pagination input are rejected.

## Layer 9 — Init parity

| Entry point | Canonical schema init | Explicit viewer | Blocking DB isolation |
| ------------- | --------------------: | --------------: | --------------------: |
| list page | yes | yes | `spawn_blocking` |
| unread count | yes | yes | `spawn_blocking` |
| mark read | yes | yes | `spawn_blocking` |
| mark all read | yes | yes | `spawn_blocking` |
| mark unread | yes | yes | `spawn_blocking` |

All five commands (`team_inbox_list_page`, `team_inbox_unread_count`, `team_inbox_mark_read`, `team_inbox_mark_all_read`, `team_inbox_mark_unread`) are registered in the same Tauri handler list.

## Layer 10 — Resolver symmetry

- Local viewer identity uses the same current-user member resolver for list, single read, and bulk read.
- Cloud cache and persisted receipt keys use the same endpoint + authenticated user + org scope.
- Project and standalone navigation both resolve raw Work Item data through the same adapter chain before opening the canonical Chat Panel Work Item tab.

## Completion verdict

- Canonical DDL changed directly; no `ALTER TABLE` compatibility path was introduced.
- Local cursor ordering and viewer-scoped receipt idempotence are tested.
- Cloud receipt storage is bounded to 1,000 entries.
- No timer or polling loop was introduced; refresh is driven by initial demand, existing project-change signals, cloud comment signals, and mutations.

**Architecture verdict: pass for the audited Team Inbox scope.**
85 changes: 85 additions & 0 deletions docs/architecture-audit-2026-07-27/TeamInboxCollaboration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Architecture Audit — Team Inbox Multi-User Collaboration

**Scope:** structured member mentions, durable viewer-scoped read receipts, authoritative unread counts, full-roster Work Item identity projection, and dual-instance UI coverage.
**Date:** 2026-07-27

## Layer 1 — Compilation correctness

- TypeScript `tsc --noEmit`: passed.
- Focused ESLint over all changed collaboration/UI files: passed.
- Twenty-four focused Vitest files: 229 tests passed after rebasing onto the latest `develop` and adding capability-gate regression coverage.
- Cloud migration was statically reviewed; live apply and live two-account E2E remain deployment validation.

## Layer 2 — Dead code and structural deduplication

- Removed the cloud mention localStorage receipt owner; server receipts are now the sole cross-device source of truth.
- `resolveMentions` and `MemberMentionChip` own repeated UUID-to-name and pill UI logic.
- Session comments load the active roster through the existing shared roster coordinator rather than adding a second fetch/cache.
- Work Item history, description creator, assignee, and reviewer all project the same roster identities.

## Layer 3 — Naming consistency

- `mentionedUserIds` is used consistently on client wire/domain models; PostgreSQL uses `mentioned_user_ids`.
- `readAt` denotes the viewer-specific receipt timestamp, while `unreadCount` denotes the authoritative full-result total.
- `markAllTeamInboxMentionsRead` is explicitly org/viewer scoped rather than implying a global Inbox mutation.

## Layer 4 — Semantic overloading

| Term | Meaning | Verdict |
| --------------- | -------------------------------------------------------------- | ----------------------------------------------------------- |
| mention | Explicit active-org member UUID attached to a comment | Never inferred from display text. |
| read | Receipt for one authenticated viewer and one mentioned comment | Separate from comment resolution or Session state. |
| unread count | Full eligible mention total outside the current page | Owned by the server response, not derived from loaded rows. |
| member identity | Stable user UUID with roster-projected display name | IDs persist; names may change without rewriting history. |

## Layer 5 — Default branch analysis

- Old cloud deployments report no `teamInboxMentions` capability, so the structured picker stays hidden.
- Comment adds without mentions retain the legacy RPC; adds with mentions require the atomic 0010 RPC and never silently drop recipients.
- The view owns optimistic read/unread presentation and per-item rollback generations. The data source serializes the corresponding durable mutations through a bounded queue, so rapid opposite actions cannot commit out of order.
- Empty, loading, pagination, filtered, and partially loaded Inbox states preserve the server unread total.

## Layer 6 — Cross-domain concept leakage

- PostgreSQL owns durable receipts, recipient validation, visibility, retention, and authoritative totals.
- Org2Cloud clients own wire validation and transport retry only.
- Team Inbox owns list/filter/optimistic presentation, not receipt persistence.
- Session comments own member selection and mention rendering.
- Work Item components own assignee/reviewer/history identity presentation.

## Layer 7 — New developer confusion test

- No caller supplies a viewer ID to receipt RPCs; `auth.uid()` is always authoritative.
- The server accepts recipient UUIDs only after validating active membership in the target org.
- The capability flag documents the required server/client rollout order.
- Local single-user assigned items and cloud mention items remain distinct data-source branches with one normalized Inbox model.

## Layer 8 — Wire protocol and serialization

- `cloud_add_session_comment_with_mentions` atomically writes the comment and its deduplicated recipient UUIDs.
- Existing `cloud_list_session_comments` keeps its signature and legacy keys, adding `mentionedUserIds`.
- Mention list rows add `readAt`; the page adds `unreadCount` and a keyset `nextCursor`.
- Receipt mutations return both the resulting `readAt` and a fresh authoritative `unreadCount`.
- Zod schemas reject malformed wire state before it enters UI state.

## Layer 9 — Init parity

- Initial Inbox load and pagination both use the same mention projection; only the first page replaces the authoritative count.
- The initial cloud projection is capability-gated. A pre-0010 backend keeps local assigned items available without attempting a missing RPC.
- Reopened comment surfaces load persisted recipient IDs from the ordinary comment list.
- Roster loading is keyed by endpoint/account/org/revision and discards stale identity results.
- Account/org changes evict the previous projection in a layout effect before paint; page and mutation completions carry a load generation and cannot repopulate the new identity with old rows.
- Both primary and secondary desktop instances exercise the production UI/data paths in the extended E2E scenario.

## Layer 10 — Resolver symmetry

- Owner, assignee, reviewer, comment author, and mentioned recipient all resolve through the active org roster.
- Mark-read, mark-unread, and mark-all share the same eligibility rules as list/count: membership, retention, deletion, visibility, and active sharing.
- Restricted Sessions are visible only to owner or active grantees across both list and count paths.
- The owner does not receive another member's targeted mention projection unless explicitly included as a recipient.

## Completion verdict

- Architecture verdict: pass for Layers 1–10 in the implemented scope.
- Deployment gate: apply cloud migration `0010_team_inbox_mentions.sql` before shipping the desktop capability-enabled experience.
- Remaining production proof: run the managed-cloud two-account E2E after the migration is applied.
98 changes: 98 additions & 0 deletions docs/architecture-audit-2026-07-27/TeamInboxThread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Architecture Audit — Team Inbox Thread and Kanban Refresh

**Scope:** Team Inbox full Work Item loading/editing, shared Work Item presentation policy, canonical Start Agent handoff, Session-tab navigation, and local/cloud Kanban manual refresh.
**Date:** 2026-07-27

## Layer 1 — Compilation correctness

- Focused Vitest suites: passed.
- TypeScript `tsc --noEmit`: passed.
- Focused ESLint: passed.

## Layer 2 — Dead code and structural deduplication

- Removed the body-only `useTeamInboxWorkItemBody` path.
- `useTeamInboxWorkItem` now resolves the full canonical item used by both `WorkItemContent` and `WorkItemProperties`.
- Moved `toWorkItemPartialUpdate` out of `WorkItemPanelView` so the Chat Panel and Team Inbox share one write-payload mapper.
- The ordinary Work Item view and Team Inbox both use one `WorkItemContent`; only an explicit presentation policy differs.
- `WorkItemThreadLayout` now owns the centered reading frame and metadata-band composition; `WorkItemThreadSection` owns the static card shell.
- To-Do and Workflow share `WORK_ITEM_THREAD_TOKENS`, while Workflow retains the existing `CollapsibleSection` state owner rather than introducing a second collapsible abstraction.
- Thread-only To-Do draft state is component-local and is never persisted until a non-empty item is committed.
- `ChatPanelWorkItemActionRequest` is a transient one-slot command envelope. It carries intent only; the canonical Work Item orchestrator remains the sole execution owner.

## Layer 3 — Naming consistency

- Added `presentation: "default" | "thread"` rather than an ambiguous boolean such as `hideSessions`.
- `start_agent` is named as a navigation action request instead of overloading ordinary `open_work_item`.
- `usePendingWorkItemAction` names the only bridge from the transient request to the canonical Work Item start command.
- Added a dedicated `open_session` navigation intent. Opening a Session no longer overloads `open_session_comment` with empty comment/thread IDs.
- `refreshKanbanSources` names the local/cloud fan-out without claiming ownership of either cache.

## Layer 4 — Semantic overloading

| Term | Meaning | Verdict |
| ------------- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| thread | One Work Item activity flow containing workflow/session cards and history | Distinct from a Session comment thread; scoped to `WorkItemContent` presentation. |
| thread layout | Stateless Work Item-domain presentation primitives | Owns composition/tokens only; it does not own persistence, collapse state, or orchestration. |
| Session open | Open/focus a Session Chat Panel tab | Explicit `open_session`; comment anchoring remains `open_session_comment`. |
| refresh | User-triggered authoritative revalidation | Local roster and cloud teammate snapshots keep their own identity/single-flight owners. |
| start request | One-shot UI intent for the matching canonical Work Item | Not workflow state and not persisted in the tab; claimed before async orchestration begins. |

## Layer 5 — Default branch analysis

- `resolveWorkItemContentSectionPolicy` handles both closed presentation variants and is unit-tested.
- `default` preserves the legacy tabs plus linked-Session table for existing consumers.
- `thread` omits that table, renders workflow/history inline, and renders output only when proof of work exists.
- Thread description transitions are explicit: read → editing → dirty → saved/cancelled. Save is disabled in editing/clean state.
- Start transitions are explicit: Inbox idle action → resolve/open canonical Work Item → publish matching request → atomically claim request → existing orchestrator validates configuration/locks and starts or reports failure.
- A claimed request is cleared before the async call, so remounts and repeated React effects cannot replay it. A non-matching Work Item cannot claim it.
- The transient channel holds at most one unclaimed request. A newer navigation intent supersedes an older unclaimed intent, preventing a hidden tab from starting unexpectedly when visited later.
- Read/update failures are explicit UI states; they do not fall back to fabricated data.

## Layer 6 — Cross-domain concept leakage

- Project persistence stays behind `projectApi` and the shared Work Item payload mapper.
- Team Inbox owns selection and presentation only.
- Thread primitives live under the Work Item component domain rather than a global shared package because the reading width, metadata band, and density are Work Item-specific.
- Agent execution remains exclusively owned by the canonical Work Item surface. Team Inbox publishes intent but does not mount a second orchestrator (which would duplicate collaboration-lock, auto-review, and stale-session lifecycles).
- Chat Panel atoms remain the sole owner of Session tab creation/focus.
- Kanban composes refresh callbacks but does not take ownership of session/cloud caches.

## Layer 7 — New developer confusion test

- The presentation policy documents exactly which legacy elements are absent.
- The thread layout API uses semantic slots (`path`, `properties`, `title`, `meta`, `action`) instead of exposing consumer-defined class bags.
- Static versus collapsible cards remain visibly consistent through one token source, while their interaction semantics stay explicit in their owning components.
- The full Work Item hook exposes `loading / ready / error` rather than conflating missing data with loading.
- Standalone items remain readable but do not expose non-functional edit controls.
- Project-scoped items expose compact shared property pills; the full property editor remains available through the canonical Work Item surface.

## Layer 8 — Wire protocol and serialization

- No new wire format was introduced.
- The extracted presentation primitives are stateless and introduce no new IPC, persistence, cache, subscription, timer, or request lifecycle.
- The start request is process-local transient UI state; it never enters tab persistence, project persistence, IPC, or the Agent wire payload.
- Work Item writes reuse the existing `WorkItemPartialUpdate` contract.
- To-Do drafts never cross that boundary; only normalized committed rows are serialized.
- Team/shared `+/-` impact is not synthesized: Kanban continues to consume authoritative local impact and cloud session metadata only.

## Layer 9 — Init parity

- No Agent initialization entry point changed. The request terminates at the same `handleStartAgent` used by the existing Work Item button.
- Manual refresh uses the same production local roster coordinator and cloud remote-session hook used by initial demand/realtime recovery.
- Tests call the source-composition helper only; rendered acceptance must still drive the real button.

## Layer 10 — Resolver symmetry

- Project-scoped reads resolve Work Item plus project metadata/repo identity; standalone reads use the standalone API and stay read-only.
- Local and cloud Kanban sources are both invoked by the manual action, while each source preserves its own scope/identity rules.
- Existing Session tabs are focused; missing tabs are created through the same open-or-focus atom for both Session cards and mention navigation.
- Work Item action resolution is symmetric for newly created and already-open tabs: both are activated first, then receive the same keyed one-shot request.

## Completion verdict

- One persistent Work Item owner, one Agent start dispatcher, one Session-tab dispatcher, and one cache owner per Kanban source.
- The Team Inbox navigation wrapper now forwards explicit child intents, so Session cards no longer collapse back to the selected row's generic Work Item destination.
- Stale Work Item reads are cancelled on selection change; overlapping writes use a monotonic generation before replacing UI state.
- Manual workflow refresh preserves the currently rendered Work Item on read failure and exposes the error banner; it does not replace success data with a transient empty state.
- Architecture verdict: pass for Layers 1–10 in the changed scope.
Loading
Loading