Skip to main content

Org topbar working tabs

The org topbar is a shell-level working set, not a second navigation tree. It combines temporary work, resumable drafts, durable record shortcuts, and live activity while keeping each state owner explicit.

The authoritative desired behavior is ../product/org-working-tabs-lifecycle-spec.md. It defines stable insertion order, retention, dialog dismissal, lifecycle handoff, persistence, and acceptance criteria.

The implemented consolidation architecture is org-working-tabs-architecture.md. It defines the single lifecycle engine, pure policy/reducer boundary, feature adapter contract, persistence coordinator, and remaining feature-payload adapter boundaries.

The comprehensive dialog and lifecycle inventory is maintained in org-working-dialog-lifecycle-audit.md. That audit defines feature coverage. This document records the current architecture and migration boundary; current behavior described here is not a substitute for the product specification.

Current state classes

StateOwnerLifetimeClose behavior
Create sessionLifecycle engine plus OrgCreateIntentProvider payload adapterUser and organization scoped; pinned or meaningful drafts survive minimize and reloadLeaving closes untouched sessions and minimizes pinned or meaningful sessions; X discards the exact draft session
Quick-note sessionLifecycle engine plus OrgQuickNoteProvider payload adapterUser and organization scoped; seven-day draft lifetimeLeaving closes an untouched note and minimizes a pinned or meaningful note; X discards only unsaved content
Active timeruseActiveTimerUser and organization scoped; survives reloadExplicit discard confirmation
Temporary record tabLifecycle engine plus OrgWorkspaceTabsProvider live handle adapterCurrent browser tab sessionConfirms removal from the topbar without deleting the record
Pinned recordLifecycle engine plus OrgPinnedWorkProvider backend adapterDurable backend preference, re-authorized on readConfirms removal from the topbar; also closes an overlapping temporary tab without deleting the record
Edit or preview dialog tabLifecycle engine plus OrgEditDialogTabsProvider live handle adapterTemporary while open; its record pin can survive route changesX closes the temporary dialog tab; pin/unpin changes only durable topbar membership

Create sessions currently remain local drafts, but create dialogs whose saved result has a canonical record adapter expose a pin-after-save control. Selecting it pins that exact lifecycle immediately; successful creation hands the same identity and immutable position to the saved record. Opening an existing record in edit or preview mode creates its temporary tab immediately. Its dialog pin promotes that same visible tab to durable topbar membership without rendering a duplicate.

Current implementation invariants

  • Every shell-local storage key is scoped by organization ID and viewer email. No draft or working-tab content is read before that identity scope resolves.
  • Temporary record tabs use sessionStorage; durable pins use backend preferences.
  • Create and quick-note sessions use localStorage because they contain recoverable work.
  • Canonical records never use shell browser storage as their source of truth. Manual calendar events and active timers are backend-backed; their remaining browser reads exist only to import and remove legacy data.
  • Pin writes are optimistic, serialized in interaction order, and held until the corresponding backend updatedAt revision reaches the subscription.
  • A delayed close or minimize always captures and targets the exact session ID that initiated it.
  • At most one create session, quick-note session, or edit dialog is active at a time; minimized siblings remain resumable.
  • Leaving a create surface or quick note removes it immediately when it is untouched and keeps it resumable only after the user enters meaningful content.
  • Saving a create session always removes the create tab. It pins the resulting record only when the user selected pin before saving and the result has a canonical saved-record adapter.
  • Supported create dialogs and new quick-note editors expose an icon-only pin-after-save control. Existing-record edit/preview dialogs expose the same position as a direct pin/unpin control. Creates without a canonical saved-record adapter do not show a misleading pin.
  • Note-level Starred state organizes Notes and is independent from shell pinning. Starred and Recent note rows expose the shell pin/unpin action; the action never mutates the note's Starred state.
  • An action pin from an older release is cleanup-only. New create flows never persist draft action IDs to backend preferences.
  • A record shown both as a durable pin and a temporary tab renders once. Closing the durable instance unpins it and removes the overlapping temporary tab.
  • Unpinning an open edit or preview dialog returns its tab to temporary status; it does not close the dialog or delete the record.
  • Route-backed edit and preview dialogs use the shared useWorkingRecordRouteDialog controller. Closing records an intentional dismissal before removing the URL parameter, so a stale query cannot reopen the dialog.
  • Clicking a topbar tab never navigates or replaces the underlying page. It resumes a shell-owned create/editor/preview surface in place. Navigation is allowed only through a separate, explicit action inside that surface.
  • Reminder launch rows and reminder working tabs use the same compact shell preview. Open reminder is the only quick-view action that transitions to the durable reminder detail route; Edit remains an explicit dialog action.
  • Pinned projects open an on-demand compact resume preview rather than a reporting modal. Open blockers lead, followed by viewer-assigned, unassigned, then team-assigned tasks, with due state and priority breaking ties. Inline completion and timer start stay bounded; task creation and explicit project/task links hand larger work back to its owning route.
  • working-record-registry.ts owns canonical record tab IDs and every route-backed record query parameter. Features pass a record type to the route controller and use the registry href builder; they do not construct these identifiers or query strings themselves.
  • ModalDialog derives an existing record's tab ID from its type, ID, and edit/preview mode. A caller-provided temporary ID is used only when no saved record exists.
  • Every topbar tab trigger uses WORKING_TAB_TRIGGER_CLASS; feature-specific chips may change content and behavior, but not their width contract.
  • Disabled escape or outside-click dismissal must not call the dialog close request.
  • Icon-only controls expose localized accessible names and pressed-state controls use aria-pressed.

Ownership boundaries

The shell owns create dialogs, quick notes, record tabs, pinned records, and the active timer, so they remain stable across org route navigation. Edit and preview tabs are adapters over route-owned dialogs: their forms, validation, dirty baselines, and save behavior remain with the owning feature. When their route unmounts, the temporary dialog adapter unregisters; if the user pinned it, the durable record tab remains and opens a shell-owned preview without changing the current route.

Adding a new topbar state requires choosing one state class above. Do not add another persistence mechanism or encode a temporary draft as a backend pin. New record pins must also be covered by backend visibility filtering.

Required migration outcome

The existing providers must converge on the shared lifecycle behavior without losing valid persisted work:

  • one immutable cross-owner added order
  • newly added lifecycles first
  • no reordering on view, resume, pin, unpin, save, autosave, or stage change
  • immediate pin retention for untouched creates
  • engagement retained independently from dirty state
  • atomic create-to-record identity and pin handoff at the same position
  • exact create/edit-stage recovery across organization route navigation
  • one canonical item across preview and edit

Migration and new tests must follow the product acceptance scenarios and the feature coverage audit. The consolidation architecture is the required implementation boundary; do not add another lifecycle state owner.

Validation

Lifecycle coverage lives in:

  • apps/app/tests/org/create-intent-context.test.tsx
  • apps/app/tests/org/quick-note-context.test.ts
  • apps/app/tests/org/pinned-work-context.test.ts
  • apps/app/tests/org/workspace-tabs-context.test.ts
  • apps/app/tests/org/edit-dialog-tabs-context.test.tsx
  • apps/app/tests/org/working-record-route-dialog.test.ts
  • apps/app/tests/org/working-record-route-coverage.test.ts
  • apps/app/tests/org/working-record-registry.test.ts
  • apps/app/tests/org/working-tab-style-coverage.test.ts
  • apps/app/tests/org/working-quick-view-strip.test.ts
  • apps/app/tests/org/reminders-quick-view.test.tsx
  • apps/app/tests/org/reminder-quick-view-content.test.tsx
  • apps/app/tests/org/working-project-quick-view.test.tsx
  • apps/app/tests/ui/modal-dialog.test.tsx
  • packages/backend/tests/org/workspace-pinned-work.test.ts
  • packages/backend/tests/org/project-workflow.test.ts