Skip to main content

Org working dialog lifecycle audit

This audit is the implementation inventory for resumable org work. It covers every org dashboard dialog family found in the source tree and separates long-lived work from confirmations and shell utilities that must remain transient.

No behavior should be changed from this audit alone. The inventory exists so the working-tab lifecycle can be implemented once, consistently, and verified feature by feature.

Implementation note (2026-07-30): the “current gap” columns below are the pre-refactor baseline that justified the work. The shared lifecycle engine, immutable ordering, create/quick-note retention, canonical preview/edit identity, timer handoff, and durable V2 pins are now implemented. Feature providers remain only as draft, callback, and render-handle adapters; they no longer define rail order, retention policy, or lifecycle identity.

The authoritative desired behavior is ../product/org-working-tabs-lifecycle-spec.md. That specification decides ordering, retention, dismissal, pinning, handoff, persistence, identity, and acceptance criteria. This audit decides which surfaces must comply.

The required consolidation boundary is org-working-tabs-architecture.md. Core rows integrate through its shared lifecycle engine and feature-adapter contract; they must not implement local variants of the policy below.

Required working-tab contract

For every resumable work surface:

  1. Genuinely adding it makes it the first working item in the topbar. Resuming an existing item does not move it. Permanent shell controls may remain before the working-item rail.
  2. Opening it also opens its dialog immediately. Registering a working item must never leave the dialog hidden.
  3. The pin belongs to the whole lifecycle, not only the current component: create draft -> saved record -> preview -> edit -> later saved versions.
  4. Pinning retains the current stage even when the draft is untouched and has never autosaved.
  5. Entering meaningful content retains the current stage when the user minimizes it or clicks outside, even when it is not pinned.
  6. Minimize and pointer-down outside hide the dialog without discarding its state. They are not close operations.
  7. An explicit close/discard operation removes an unpinned session. A pinned saved record remains as its record working item unless the user explicitly unpins it.
  8. Saving a pinned create session atomically replaces that create item with the saved record item in the same position. It must not render both.
  9. Successful explicit save completes and removes an unpinned create item by default. Background autosave never counts as completion.
  10. Navigating around the org app does not destroy a pinned or engaged working stage. Resuming restores the same stage and draft, not a generic substitute.
  11. Reopening, resuming, pinning, saving, or changing stage never changes the item's immutable added order.
  12. Record identity is stable across route changes and transitions between preview and edit. A lifecycle transition must not create an unrelated tab.

Scope and source count

The static scan covers apps/app/src/app/[locale]/org/(dashboard).

The tree currently contains 126 dialog-primitive call sites, including wrapper composition:

Primitive or wrapperCall sites
ModalDialog58
CreateDialogScaffold15
Radix DialogContent10
AlertDialogContent25
ConfirmDialog18

The working system currently recognizes:

  • 17 shell create-action types
  • 17 saved-record pin types
  • 13 create types that promote a selected pin to a saved record

The tables below catalogue product surfaces rather than counting a wrapper and the primitive it composes as two separate dialogs.

Current state owners and systemic gaps

State ownerCurrent responsibilityCurrent gap
OrgCreateIntentProviderShell-owned create sessions in localStorage; one active create IDNew sessions append; pin intent does not retain an untouched session; only a stored draft keeps a minimized session
OrgQuickNoteProviderQuick-note sessions in localStorage; one active quick-note IDNew sessions append; closing an untouched pinned draft removes it; create-to-record pinning is special-case note logic
OrgEditDialogTabsProviderTemporary open edit/preview tabsTabs append; registration is effect-driven; only page-local component state owns the actual draft
OrgWorkspaceTabsProviderTemporary record tabs in sessionStorageNew tabs prepend within this owner, but no immutable cross-owner added order exists
OrgPinnedWorkProviderDurable saved-record pins in backend preferencesPinning or re-pinning can move a record, violating stable insertion order; it also pins canonical record identity rather than the live dialog stage/draft
OrgWorkingQuickViewStripCombines durable pins, temporary tabs, create sessions, quick notes, timer, and edit tabsEach owner contributes a differently ordered array; sortWorkingStripEntries returns insertion order unchanged
Route/page stateMost existing-record editors and previewsNavigation unmounts the editor; a durable pin can reopen a preview, but not the same live edit draft

Confirmed ordering defect

The topbar is not using one immutable addition sequence:

  • durable pins are stored with newest/moved item first
  • workspace record tabs prepend a new record within their own owner
  • create sessions append to createActions
  • quick-note sessions append to sessions
  • edit/preview dialog tabs append to tabs
  • the strip concatenates those groups and performs no final ordering

Therefore genuinely new work cannot reliably become first across owners, and later pin or persistence operations can change an established position. Both behaviors violate the stable insertion rule.

Confirmed pin-retention defect

The create pin currently stores pinResultAfterCreate. It is an instruction to pin a future saved record, not a pin on the current create session. minimizeCreateAction still removes the session when no persisted draft exists. Quick notes have the same shape: pinResultAfterSave is ignored when an empty unsaved note is hidden.

Existing-record dialogs have a different defect. Their pin persists the record, but their form state remains route-owned. Navigating away can replace the live edit stage with a generic shell preview.

Create lifecycle inventory

All rows in this section are core resumable work and should use the required contract.

FeatureCreate surfaceDraft nowPinned save handoff nowGap
ProjectsProjectCreateDialogPersisted by create-session draft storageYes -> projectPin does not retain an untouched create stage; ordering appends
Project tasksProjectTaskDialog in create modePersistedYes -> projectTaskSame; nested parent-create return is separate orchestration
ClientsClientCreateDialogPersistedYes -> clientSame
Timershell TimerDialog before startPersistedNo saved-record pin transition; active timer is specialPin control cannot carry the lifecycle; timer is force-positioned separately
Time entriesTimeEntryCreateDialogPersistedYes -> timeEntrySame untouched-pin and ordering defects
NotesNoteComposeDialog in create modePersistedYes -> noteSeparate from quick-note lifecycle despite the same saved record
Quick notestopbar quick-note composerPersisted in quick-note sessionsNew notes can pin -> noteUntouched pinned note is removed on hide; ordering appends
CalendarEditableCalendarEventDialog in create modePersistedYes -> calendarEventSame untouched-pin and ordering defects
RemindersReminderDialog in create modePersistedYes -> reminderSame
ContractsContractCreateDialogPersistedYes -> contractSame
InvoicesInvoiceCreateDialogPersistedYes -> invoiceAccess/Stripe/empty-client fallback modals are alternate render states of the same create lifecycle
SubscriptionsSubscriptionCreateDialogPersistedYes -> subscriptionSame
QuotesQuoteCreateDialog in create modePersistedYes -> quoteSame
LinksLinkCreateDialogPersistedYes -> linkSame
DriveDriveCreateDialog / CreateDriveItemDialog for upload, folder, and docPersisted per kindYes -> driveFile or driveFolderThree draft shapes share one create action; transition and ordering need one lifecycle identity
Catalog productsCatalogProductCreateDialogPersistedNo record pin type or promotionThe create action has a registry identity, but no pin control or saved-product tab exists
Catalog pricesCatalogPriceCreateDialogPersistedNo record pin type or promotionThe create action has a registry identity, but no pin control or saved-price tab exists
PortalsPortalCreateDialogPersistedNo record pin type or promotionThe create action has a registry identity, but no pin control or saved-portal tab exists

Saved-record lifecycle inventory

Every row is a saved-record working lifecycle. A pin must preserve record identity and the active preview/edit stage. Dirty edit state must survive hiding and navigation.

Record typePreview surfaceEdit surfaceCurrent topbar behaviorCurrent lifecycle gap
noteNote working chip / quick editorQuick editor, compose dialog, and Notes page editorDurable and temporary note items exist; saved note sessions are deduplicatedMultiple state owners; pinned create/edit stages do not share one lifecycle identity
projectProject quick-view chipProjectEditDialogEdit registers a temporary dialog tab; pin stores the projectNavigation loses live edit draft and resumes a preview
clientClient quick-view chipclients-list edit ModalDialogEdit registers a temporary dialog tabSame
clientDriveClient-drive quick-view workspaceDrive workspace operationsDurable/temporary record itemNo single resumable operation stage for nested create/request/share work
driveFolderDrive/client-drive workspacerename/move/share operationsRecord can be pinnedOperations are untracked, route-owned dialogs
driveFileDriveFilePreviewDialogdocument edit in ClientDriveManagementDialogsPreview/doc edit can register; record can be pinnedEdit state and preview stage are not one durable lifecycle
linkLinkDetailsDialogLinkEditDialogPreview/edit register; record can be pinnedRoute navigation loses active edit draft/stage
reminderReminder working quick viewReminderDialogQuick view is shell-owned; edit can registerPreview and edit use separate owners; edit draft is not durable
projectTaskTask working quick viewProjectTaskDialog in edit modeEdit can register; record can be pinnedLive edit state is page-owned
projectUpdateUpdate working quick viewProjectUpdateEditorDialogEdit can register and disables outside closeNavigation still destroys the editor; pin retains record only
clientContactContact working quick viewContactDialog in edit modeEdit can register; record can be pinnedCreate mode is not represented by this saved-record ID; edit state is page-owned
invoiceInvoice working previewNo general invoice-edit dialog; detail actions are separateRecord can be pinnedDetail-operation stage is not represented
subscriptionSubscription working previewSubscriptionEditDialog plus lifecycle operation dialogsRecord can be pinnedEdit and operations do not register as stages of the pinned subscription
contractContractPreviewDialogContractCreateDialog with contractIdPreview/edit register; record can be pinnedPreview -> edit -> save is split across component-local state
quoteQuote working previewQuoteCreateDialog with quoteRecordIdEdit registers; record can be pinnedLive edit draft does not survive route navigation
calendarEventmanual/project event dialogs and event quick viewEditableCalendarEventDialogPreview/edit can register; record can be pinnedManual/project preview, editable event, and create/save transitions are separate paths
timeEntryTime-entry working quick view and TimeEntryReviewDialogTimeEntryEditDialogEdit can register; record can be pinnedReview/edit/return-workflow stages are not one durable lifecycle

Feature-by-feature dialog catalogue

Legend:

  • Core: must become a resumable working lifecycle.
  • Parent stage: should be retained as a stage of its owning core record, rather than a separate unrelated tab.
  • Transient: must not become a working tab.
  • Shell utility: intentionally outside the working-record model.

Shell and cross-feature

SurfaceClassRequired treatment
Create host for all 17 create typesCoreOne ordered lifecycle registry
Quick-note composer and saved-note quick editorCoreMerge with note lifecycle
Timer draft and active timerCoreStable draft -> running timer -> saved time-entry lifecycle
Client focus selector dialogShell utilityNever a working item
Client-focus navigation guardTransientConfirmation only
Launchpad full-screen dialogShell utilityNever a working item
Search dialogShell utilityNever a working item
Calendar/reminders/notes topbar quick-view popoversShell utility until they open a recordRecord selection activates the corresponding core lifecycle
Create-draft discard dialogTransientConfirmation only
Working-item remove/unpin confirmationsTransientConfirmation only
Timer save/close and discard confirmationsTransientConfirmation only
Changelog, hotkeys, feedback, and feature-request dialogsShell utilityNever working items

Clients and portal configuration

SurfaceClassCurrent behavior / required treatment
Create clientCoreCovered by shell create session; fix pin retention/order
Edit client from client listCoreTemporary edit tab; make draft/stage shell-owned
Create contactCoreCurrently normal dialog with no create working identity
Edit contactCoreExisting-record tab only while route remains mounted
Delete clientTransientDestructive confirmation
Create portalCoreCreate session exists but has no saved-record promotion
Invite portal contactParent stageTie to portal/client lifecycle if retained
Configure portal relationship contactsParent stageTie to portal/client lifecycle; local dirty form currently dies on navigation
Revoke invite / disable portal contactTransientDestructive confirmation
Disable portalTransientConfirmation

Projects

SurfaceClassCurrent behavior / required treatment
Create projectCoreCreate session; fix retention/order
Edit projectCoreTemporary edit tab; draft not navigation-safe
Create project taskCoreCreate session; parent context must survive nested create
Edit project taskCoreTemporary edit tab; draft not navigation-safe
Create/edit project updateCoreTemporary edit tab for saved update; outside click is currently blocked
Create/edit blockerParent stageTie to project lifecycle; currently raw route-owned dialog
Archive/restore/delete projectTransientConfirmations
Complete/delete taskTransientConfirmations
Resolve blockerTransientConfirmation
Publish/delete project updateTransientConfirmations

Calendar and reminders

SurfaceClassCurrent behavior / required treatment
Create calendar eventCoreCreate session; fix retention/order
Edit editable calendar eventCoreTemporary record stage
Preview manual calendar eventCoreTemporary preview stage
Preview project-derived calendar eventCoreTemporary preview stage tied to canonical project context
Create reminderCoreCreate session
Edit reminderCoreTemporary record stage
Reminder compact quick viewCore previewShell-owned preview should be the same lifecycle
Delete reminderTransientConfirmation

Notes

SurfaceClassCurrent behavior / required treatment
Create note compose dialogCoreSeparate create-session owner
Quick-note create composerCoreSeparate quick-note owner
Edit saved note in quick editorCoreLocal-storage note session
Edit saved note in compose dialogCoreMust use same saved-note lifecycle
Notes page editorCore non-dialog stageMust coordinate with the same lifecycle instead of becoming a competing editor
Discard unsaved noteTransientConfirmation

Drive

SurfaceClassCurrent behavior / required treatment
Upload fileCoreCreate lifecycle
Create folderCoreCreate lifecycle
Create documentCoreCreate lifecycle
Edit documentCoreSaved driveFile stage
Preview fileCore previewSaved driveFile stage
Client-drive create dialogCoreMust use the same upload/folder/doc lifecycle as top-level Drive
Rename itemParent stageRetain under driveFile/driveFolder if dirty
Move itemParent stageRetain under owning record if needed
Share itemParent stageRetain under owning record if dirty
Create file requestCore or parent stageNeeds a canonical request record type before it can be independently pinned
Create/manage contributor access linkCore or parent stageNeeds a canonical access-link record type or explicit parent ownership
Archive/delete/move-to-trashTransientConfirmations

Contracts

SurfaceClassCurrent behavior / required treatment
Create contractCoreCreate session
Preview contractCore previewTemporary dialog tab
Edit contractCoreSaved-record edit stage
Upload attachmentParent stageTie to contract lifecycle if retained
Publish contractTransientConfirmation
Permanently delete contractTransientConfirmation
SurfaceClassCurrent behavior / required treatment
Create linkCoreCreate session
Preview linkCore previewTemporary dialog tab
Edit linkCoreTemporary edit tab
Archive/delete linkTransientConfirmations

Billing records

SurfaceClassCurrent behavior / required treatment
Create invoiceCoreCreate session; fallback access/setup states are part of the same lifecycle
Preview invoiceCore previewSaved record quick view
Create credit noteParent stageTie to invoice lifecycle; currently an alert-form dialog
Refund invoice/paymentParent stageTie to invoice lifecycle; currently an alert-form dialog
Write off invoiceParent stageTie to invoice lifecycle
Void invoiceTransient or parent stageConfirmation with invoice context
Delete invoiceTransientDestructive confirmation
Create subscriptionCoreCreate session
Preview subscriptionCore previewSaved record quick view
Edit subscriptionCoreCurrently an unregistered page-owned modal
Manage trialParent stageTie to subscription lifecycle
Manage pauseParent stageTie to subscription lifecycle
Manage scheduleParent stageTie to subscription lifecycle
Manage discountParent stageTie to subscription lifecycle
Cancel nowTransient or parent stageDestructive operation with local input
Cancel scheduled changeTransientConfirmation
Create quoteCoreCreate session
Edit quoteCoreSaved-record edit stage
Preview quoteCore previewSaved record quick view
Accept/cancel/delete quoteTransientConfirmations

Catalog

SurfaceClassCurrent behavior / required treatment
Create productCoreDraft persists, but no product record pin/promotion exists
Create priceCoreDraft persists, but no price record pin/promotion exists

Time

SurfaceClassCurrent behavior / required treatment
Timer draftCoreSpecial create action
Running timerCoreServer-backed shell activity; starting it adds it first, then its position remains stable
Create time entryCoreCreate session
Edit time entryCoreTemporary edit tab
Review time entryCore previewRaw route-owned dialog
Return time entry with reasonParent stageTie to time-entry lifecycle
Delete time entryTransientDestructive confirmation
Create/link note from timerNested core transitionParent timer must remain resumable while note creation becomes active

Settings and administration

These are catalogued because they are dialogs, but they should not automatically enter the general working rail without an explicit product decision. Most are bounded configuration or security operations rather than client work records.

FeatureDialogsDefault class
Analyticsprofile editor, adjustment editor, delete profileSettings-local; delete is transient
API accessissue credential, reveal/acknowledge issued credentialSettings-local/security-sensitive
Billing settingsplan-interest form, initial subscription checkoutSettings-local
Integrationspayment recovery, Stripe connection start, activation, cancel, webhook repairSettings-local; cancel is transient
Securityaccount-security/step-up dialogs used by the org settings surfaceSecurity-sensitive shell utility
Teaminvite member, remove member, transfer ownershipInvite is settings-local; remove/transfer are transient destructive dialogs
Webhookscreate endpoint, reveal/acknowledge secretSettings-local/security-sensitive
Advanced settingsdestructive organization actionTransient

Implementation coverage matrix

The eventual implementation is incomplete until every Core row has tests for the applicable columns.

BehaviorCreateExisting editPreviewQuick noteNested parent stage
Opens immediatelyRequiredRequiredRequiredRequiredRequired
New lifecycle is inserted firstRequiredRequiredRequiredRequiredRequired only when independently added
Existing lifecycle activation preserves orderRequiredRequiredRequiredRequiredRequired
Stage handoff preserves orderRequiredRequiredRequiredRequiredRequired
Pin retains untouched stageRequiredRequiredRequiredRequiredInherit parent
Meaningful hide retains stageRequiredRequiredN/ARequiredRequired
Pointer-down outside hidesRequiredRequiredRequiredRequiredRequired
Minimize hidesRequiredRequiredRequiredRequiredRequired
Navigation preserves stageRequiredRequiredRequiredRequiredRequired
Resume restores exact stageRequiredRequiredRequiredRequiredRequired
Save keeps selected pinRequiredRequiredN/ARequiredInherit parent
Create -> record transitionRequiredN/AN/ARequired for new noteWhere a record is created
No duplicate old/new itemRequiredN/ARequired across preview/editRequiredRequired
Explicit close/discard removes unpinned stageRequiredRequiredRequiredRequiredRequired

Decisions already established by this audit

  • Ordering must be driven by one immutable cross-owner added-order field, not array concatenation, activity, update, view, save, or pin time.
  • Pin state must live on one lifecycle item that can change stage and canonical identity.
  • “Pinned,” “engaged,” and “dirty” are distinct states. Pin and engagement are retention reasons; dirty controls persistence and discard warnings.
  • A pin on an unsaved create session is real immediately.
  • Page-local edit state cannot satisfy navigation-safe resumability.
  • Preview and edit are stages of one saved-record lifecycle, not unrelated topbar items.
  • Confirmation dialogs, destructive alerts, shell search, launchpad, client focus, support dialogs, and security prompts do not become working items.
  • Parent-stage dialogs need explicit ownership instead of silently becoming unrelated tabs.

Implemented consolidation

  1. @repo/working-lifecycle now owns the canonical vocabulary, reducer, policies, immutable order keys, selectors, and invariant tests.
  2. OrgWorkingLifecycleProvider owns lifecycle membership, active visibility, canonical deduplication, hide/cancel/remove policy, and storage projections.
  3. Create, quick-note, timer, temporary-record, pinned-record, and edit-dialog entry points register through that provider.
  4. The topbar projects the compatibility render adapters through the canonical immutable order. No view, edit, save, autosave, or pin action changes order.
  5. Durable saved pins dual-read/write pinnedWorkingRecordsV2, containing only canonical identity and immutable order key. A bounded Convex migration backfills those records and removes obsolete backend create-action pins. Its synchronous runner and verifier are wired into both GitHub and container-start Convex deployment paths.
  6. Pure engine, storage, UI integration, schema, and migration tests cover the default lifecycle rules. Feature-specific draft codecs remain feature-owned.