Org Working Tabs Lifecycle Spec
Status and authority
This document is the product source of truth for the organization topbar working-item rail and every resumable dialog lifecycle that participates in it. It defines the intended behavior before implementation begins.
The supporting documents have different jobs:
../development/org-working-tabs-architecture.mddefines the single-source implementation architecture and migration plan.../development/org-working-dialog-lifecycle-audit.mdinventories every current create, preview, edit, quick-note, timer, nested, transient, settings, and shell dialog family.../development/org-topbar-working-tabs.mddocuments the current implementation and the migration boundary.- This specification decides the required product behavior. Where current code or older documentation disagrees with this file, this file wins.
Purpose
Working tabs let a person begin or inspect work, temporarily leave it to find information elsewhere in the app, and resume exactly where they stopped.
The rail is a stable workbench, not navigation history:
- genuinely new work is added at the front
- existing work never jumps around merely because it was viewed again
- meaningful or deliberately pinned work survives being hidden
- a lifecycle keeps the same place as it moves from create to saved preview or between preview and edit
- discarding a draft and removing a saved shortcut are deliberate actions
This is the default behavior for all core organization work surfaces. A feature may differ only when its product semantics require it and the exception is documented in this specification and in the lifecycle audit.
Vocabulary
| Term | Meaning |
|---|---|
| Working item | One entry in the topbar rail representing a resumable unit of work |
| Lifecycle | The continuous identity of that work across create, saved preview, edit, and related stages |
| Stage | The current surface within a lifecycle, such as create, preview, edit, or a parent-owned operation |
| Lifecycle ID | Stable identity for an unsaved lifecycle |
| Canonical record ID | Saved record identity, expressed conceptually as record type plus record ID |
| Added order | Immutable sequence assigned only when a lifecycle genuinely enters the rail |
| Active | The working item's surface is currently visible |
| Hidden | The item remains resumable in the rail but its surface is not visible |
| Engaged | The person has entered meaningful work or otherwise begun a task that must remain resumable |
| Dirty | Current draft differs from its most recently persisted baseline |
| Pinned | The person explicitly chose to retain the lifecycle in the working rail |
| Handoff | An identity-preserving transition, such as create draft to saved record |
| Remove | Take the lifecycle out of the working rail without deleting a saved record |
| Discard | Intentionally abandon unsaved changes or an unsaved lifecycle |
engaged and dirty are intentionally different. Autosave may make an
engaged draft clean, but it must not make the working item disappear. Dirty
state controls save and discard warnings; engagement is a retention reason.
Non-negotiable product invariants
- A newly added lifecycle appears as the first working item after permanent shell controls.
- Each lifecycle receives one immutable added order.
- Viewing, resuming, hiding, editing, pinning, unpinning, saving, autosaving, or changing a lifecycle stage never changes that order.
- A create-to-record or preview-to-edit handoff keeps the same working item and the same position.
- Opening a lifecycle that is already in the rail activates the existing item. It neither duplicates nor reorders it.
- Removing a lifecycle ends that rail membership. Adding it again later is a genuinely new addition and therefore places it first.
- An empty, unpinned create that is hidden or cancelled disappears as though it was never opened.
- Pinning an empty create immediately creates a retainable draft lifecycle, even before any field is changed or autosaved.
- Meaningful unpinned work remains resumable when hidden.
- Pin state belongs to the lifecycle, not to a single dialog component or stage.
- Successful create handoff is atomic: the unsaved and saved identities never render as two working items.
- Navigation within the organization app cannot destroy a retained lifecycle or replace its exact draft with a generic preview.
- Failed saves leave the lifecycle, stage, pin state, draft, and position unchanged.
- Removing a saved record from working tabs never deletes the underlying record.
- Organization and user scope are strict. Working state must never leak to a different organization or signed-in person.
Stable insertion order
Ordering rule
The visual order is newest genuinely added lifecycle first. It is not based on the last item viewed, focused, updated, saved, autosaved, or pinned.
Conceptually, the rail sorts only by an immutable addedOrder. Fields such as
lastViewedAt, updatedAt, savedAt, or pinnedAt may support other product
needs, but they must never decide working-tab position.
The added order persists for as long as the lifecycle remains retained. It survives hide/resume, refresh, draft recovery, and create-to-record handoff. For durable saved pins it also survives cross-device loading; synchronizing a pin must not assign it a new position merely because another device read or updated it.
Events that create a new position
- opening a new create lifecycle
- opening a saved record that is not already represented
- starting a separate quick-note lifecycle
- starting an independent nested record lifecycle
- pinning a saved record that is not already in the rail
- re-adding a lifecycle after it was explicitly removed
Each is inserted first.
Events that preserve the existing position
- clicking or focusing an existing working tab
- reopening the same record from a route, list, search, notification, or link
- hiding and later resuming a dialog
- pinning or unpinning an existing working item
- typing, validation, autosave, or explicit save
- create-to-record handoff
- preview-to-edit or edit-to-preview transition
- a title, status, permission, or other metadata update
- a child dialog returning to its parent stage
Canonical example
- A project create lifecycle is added and pinned.
- Saving changes that same working item into the project preview. Its position does not change.
- A new note is added afterward. The note is now before the project.
- The person clicks the project working tab again.
- The project opens, but the note remains before it.
The rail therefore reflects when work joined the workbench, not what the person looked at most recently.
Removal and re-addition
If the project in the example is removed from the rail and later opened or pinned again, that later action creates a new rail membership. It receives a new added order and appears first.
Lifecycle state model
Every core working item must be representable by one shared lifecycle shape, regardless of which feature renders it.
| Property | Product meaning |
|---|---|
| Lifecycle identity | Stable identity before save and across all unsaved stages |
| Canonical identity | Optional until a record exists; stable after handoff |
| Current stage | Create, preview, edit, or a documented parent-owned stage |
| Added order | Immutable position token for this rail membership |
| Visibility | Active or hidden |
| Pin state | Explicit retention selected by the person |
| Engagement | Whether meaningful work has begun |
| Dirty state | Whether unsaved changes differ from the persisted baseline |
| Persistence scope | Memory, browser session, same-device durable draft, or backend durable pin |
| Parent relationship | Optional owner when a substage belongs to another lifecycle |
Features may store additional draft or view data, but they must not invent independent ordering, pinning, or close semantics.
Retention rules
A working item remains in the rail while at least one retention reason applies:
- its surface is active
- it is engaged
- it is pinned
Dirty state is evidence of engagement but is not the only form of engagement. For example, a successful background autosave can make a draft clean while the person still expects to resume it.
Successful explicit completion is different from autosave. When the default flow says a completed unpinned create or edit closes, that completion consumes the temporary engagement reason and hides the surface. Pinning is the explicit choice that carries the lifecycle into its saved preview stage.
| State when hidden | Required result |
|---|---|
| Empty, unpinned create | Remove immediately |
| Empty, pinned create | Keep as a hidden draft |
| Engaged, unpinned create | Keep as a hidden draft |
| Engaged, pinned create | Keep as a hidden draft |
| Clean, unpinned saved preview | Remove its temporary rail membership |
| Pinned saved preview | Keep |
| Existing-record edit with meaningful work | Keep the exact edit stage, pinned or not |
| Existing-record edit with no meaningful work and unpinned | Remove on explicit close; hide behavior may keep it only for the current active session |
| Active timer or equivalent server-backed work | Keep according to that feature's active-work lifecycle |
An unpinned engaged draft may be subject to a documented abandonment policy, but it must never disappear merely because autosave ran or the user navigated away. A pinned lifecycle is never silently expired.
Opening and activation
New create
Opening a create action:
- creates a lifecycle
- adds it first in the working rail
- opens its dialog immediately
- begins empty, unpinned, unengaged, and active
Merely registering the tab without showing the dialog is invalid.
Existing saved record
Opening a saved record that is not in the rail:
- creates one temporary record lifecycle
- adds it first
- opens its requested preview or edit stage
Opening a saved record already represented in the rail activates that same lifecycle and requested stage without changing its position.
Duplicate entry points
Routes, collection rows, search results, notifications, quick views, and topbar tabs must resolve through the same canonical lifecycle identity. Concurrent or repeated opens must not produce duplicates.
Dialog dismissal semantics
Controls must describe their effect consistently. “Hide” and “discard” are not synonyms.
Pointer outside, Minus, and Escape
These are non-destructive hide or minimize actions:
- the surface becomes hidden
- retained state remains exactly resumable
- an empty, unpinned, unengaged create is removed because it has no retention reason
- dirty or pinned state is never discarded
If a particular modal must disable pointer-outside or Escape dismissal while a critical operation runs, those gestures do nothing. They must not be converted into discard.
Dialog X and Cancel
For create and edit stages, dialog X and Cancel express an intent to cancel the current draft or edits:
- empty, clean, unpinned work can be removed immediately
- dirty or pinned unsaved work requires confirmation before discard
- confirming discard removes the unsaved lifecycle, or abandons the edit stage according to the saved-record rules
- declining returns to the unchanged dialog
For a read-only saved preview, X or Cancel only hides the preview. A pinned record remains in the rail; an unpinned clean preview loses its temporary rail membership.
The UI may offer a separate Minus control specifically to make the hide-versus-cancel distinction obvious.
Topbar tab X
The X on a working tab means “remove from working tabs”:
- an empty unsaved lifecycle is removed
- a dirty, engaged, or pinned unsaved lifecycle requires confirmation because removal would also discard its resumable draft
- a clean saved record is removed or unpinned without deleting the record
- a saved record with unsaved edits requires confirmation before those edits and the rail membership are removed
Confirmation copy must name the actual consequence. It must never imply that removing a saved working tab deletes the underlying project, note, invoice, file, event, or other record.
Pin behavior
The dialog pin means “keep this in working tabs.”
Selecting pin
- applies immediately to the current lifecycle
- does not wait for a successful save
- retains an untouched create by creating its draft identity and persistence shell
- follows the lifecycle through every later stage
- does not change the item's added order or visual position
Clearing pin
Unpinning removes one retention reason; it is not a close operation:
- an active item stays active
- a hidden engaged item stays temporarily resumable
- a hidden clean item with no other retention reason may be removed
- unpinning a saved record does not delete it
- unpinning never changes order while the item remains
Visual and accessible state
The pin control must:
- occupy a consistent dialog-header position where the surface supports working tabs
- expose a localized accessible name
- use
aria-pressedor an equivalent programmatic selected state - communicate pinned state through more than color alone
- never appear when the feature cannot honor the pin lifecycle
Create lifecycle
Empty and untouched
| Action | Unpinned | Pinned |
|---|---|---|
| Pointer outside, Minus, or Escape | Remove | Hide and retain |
| Dialog X or Cancel | Remove | Confirm discard, then remove if confirmed |
| Topbar X | Remove | Confirm discard, then remove if confirmed |
Meaningful work entered
| Action | Unpinned | Pinned |
|---|---|---|
| Pointer outside, Minus, or Escape | Hide and retain | Hide and retain |
| Dialog X or Cancel | Confirm discard | Confirm discard |
| Topbar X | Confirm discard and remove | Confirm discard and remove |
| Background autosave | Keep; update baseline only | Keep; update baseline only |
Successful explicit save
For an unpinned create, successful explicit save completes the task and removes the create working item by default. It clears the completed create stage's temporary engagement retention. The saved record remains available through its normal feature surface.
For a pinned create:
- the save creates the canonical record
- the same lifecycle receives the canonical identity
- its stage becomes the saved record preview, unless the feature has a documented post-save stage
- pin state remains selected
- added order and visual position remain unchanged
- the unsaved alias disappears atomically
There must never be a frame or persisted state containing both the create item and its saved-record replacement.
Failed save
Validation or server failure leaves the existing lifecycle visible with its draft, pin state, engagement state, stage, and order unchanged. Errors are shown in the owning surface. A failed save is never treated as completion.
Background autosave
Autosave persists recovery data; it does not complete the person's task. Therefore it:
- does not close or hide the dialog
- does not remove the working item
- does not reorder the item
- may clear dirty state while engagement remains true
- must not trigger a saved-record handoff unless the product action truly created the canonical record
Saved preview and edit lifecycle
Preview and edit are stages of one saved-record lifecycle.
- Preview to edit keeps identity, pin, and position.
- Edit save updates the same record and lifecycle.
- Hiding a retained edit must restore the exact draft and substage, not a fresh generic preview.
- Opening the same record elsewhere activates this lifecycle instead of creating another preview or edit item.
- A pinned edit remains a pinned saved record after save.
- An unpinned edit that the person explicitly completes may close and leave the rail by default.
- A background save does not imply explicit completion and must not remove an engaged edit.
- Cancelling edits may return a pinned saved item to its clean preview stage; it does not unpin the record unless the person explicitly removes or unpins it.
Nested and parent-owned dialogs
Nested work falls into one of two categories.
Independent child record
If the child creates or edits a separately resumable record:
- the parent lifecycle remains retained in its original position
- the child receives its own lifecycle and is inserted first
- the child's pin and save behavior are independent
- completing or cancelling the child restores the parent surface without reordering the parent
- parent draft state and focus context remain intact
Example: starting a new note from a timer creates a note lifecycle while the timer lifecycle remains available.
Parent-owned stage
If the child is only an operation within the parent record:
- it does not create an unrelated working tab
- the parent's current stage changes
- the parent keeps its identity, pin, and order
- hiding and resuming restores that substage when meaningful work would otherwise be lost
- completion or cancellation returns to the appropriate parent stage
Examples include a return-reason workflow for a time entry or a relationship configuration stage owned by a client or portal lifecycle.
Transient confirmations launched from either category never become working items.
Identity, deduplication, and handoff
Unsaved identity
Every create or independent quick-note session receives a unique lifecycle ID. Two intentionally separate create drafts of the same type remain separate working items.
Saved identity
A saved record has one canonical working identity for the organization and viewer. Preview, edit, and route variants resolve to it.
Handoff
Create save maps the lifecycle ID to the new canonical record identity while preserving its added order. All rail, dialog, pin, and persistence consumers must observe the transition as one atomic lifecycle update.
Rare identity collision
A truly new create should not collide with an existing canonical record. If an import, retry, or reconciliation does produce two aliases for the same record, the UI must merge them deterministically and retain only one. Prefer the already-established canonical lifecycle and its stable position; never show two tabs or silently discard unsaved content.
Persistence and recovery
Persistence level follows the kind of state:
| State | Required scope |
|---|---|
| Active visibility | Current app runtime |
| Temporary clean saved-record tab | Current browser tab/session |
| Engaged unpinned create or edit draft | Same user, organization, and device |
| Pinned unsaved draft | Same user, organization, and device; no silent expiry |
| Pinned saved record | Durable backend preference, available across devices |
| Active server-backed work such as a running timer | Durable feature state |
Cross-device unsaved draft synchronization is not required unless a dedicated backend draft system is introduced. The UI must not imply cross-device recovery for locally stored drafts.
All persisted working state must:
- be scoped by organization and authenticated user
- wait for scope resolution before reading or rendering private content
- survive navigation within the same organization
- tolerate refresh and browser restart at the level promised above
- use versioned storage with safe migration
- reject or quarantine malformed or stale data without deleting valid siblings
- clear inaccessible record shortcuts after authorization is resolved
- avoid exposing one user's draft after sign-out or account switching
Unpinned abandoned drafts may have a documented cleanup period. Cleanup must never silently evict pinned drafts, dirty edits, or active server-backed work. If the rail has a capacity limit, it must ask the person to resolve retained items instead of evicting them.
Exceptional and failure states
Save in progress
Duplicate submission is prevented. Hide behavior may be temporarily disabled when required for correctness, but the item remains in place and retains pin state.
Offline or interrupted request
Recoverable draft data remains local. The lifecycle does not hand off until the server confirms the canonical record. Retry cannot create a duplicate working item.
Record deleted elsewhere
The working item shows an unavailable/deleted state, preserves unsaved local edits for recovery where safe, and offers an explicit removal path. It does not silently redirect to another record.
Permission revoked
After authorization resolves, protected record data is removed from view. Any safe local recovery or discard choice must avoid leaking inaccessible server data. The rail does not continuously retry or reorder the failed item.
Multiple browser tabs or version conflict
Conflict handling belongs to the owning feature, but resolving, reloading, or
saving a conflict never changes rail order. A backend pin update must not use a
record's general updatedAt as its position.
Unsupported or temporarily unavailable adapter
Persisted work is not discarded. The working item may show a recoverable “temporarily unavailable” state until its renderer or migration is available.
Scope by dialog class
Core working lifecycles
The standard applies to:
- all shell create actions and meaningful quick-note sessions
- saved-record previews and editors
- active timer and time-entry transitions
- independently resumable nested record work
- parent-owned stages whose state would otherwise be lost during navigation
The complete, feature-by-feature set is maintained in the dialog lifecycle audit. Every row marked Core must adopt this specification. Every row marked Parent stage inherits the owning lifecycle unless the audit explicitly promotes it to an independent record.
Transient dialogs
Confirmations, destructive alerts, authentication/security prompts, secret reveal acknowledgements, and other bounded decisions do not enter the working rail. They remain attached to their owning action.
Shell utilities
Search, launchpad, client focus, changelog, hotkeys, feedback, and similar shell utilities do not become working items. Selecting a real record from a utility opens or activates that record's lifecycle.
Settings and administration
Settings-local and administration dialogs are excluded by default. They join the working rail only through an explicit product decision documenting why the operation must be resumable across org navigation.
Accessibility and interaction requirements
- Every dialog has an accessible title.
- Icon-only Pin, Minus, X, and other controls have localized accessible names.
- Pin exposes a programmatic pressed state.
- Active, pinned, engaged/unsaved, error, and unavailable states are not communicated by color alone.
- Keyboard activation matches pointer activation.
- Escape follows the same non-destructive hide rule when dismissal is enabled.
- Focus returns to a sensible trigger or destination when hiding, cancelling, or completing work.
- Resuming restores useful focus within the surface without changing rail order.
- Confirmation focus is trapped and returns to the unchanged draft when the person declines.
- Overflow and horizontal scrolling preserve the stable item order.
Acceptance scenarios
These scenarios are release requirements for the shared lifecycle and every applicable core feature.
- WT-01 — Untouched unpinned create: Open Create Project. It appears first and opens. Click outside. The dialog and working item disappear.
- WT-02 — Untouched pinned create: Open Create Project and select Pin without typing. Click outside or Minus. The dialog hides and its working item stays. Resume it and see the same empty draft with Pin selected.
- WT-03 — Meaningful unpinned create: Type into an unpinned create and click outside. It hides but remains resumable. Background autosave does not remove it.
- WT-04 — Explicit dirty cancel: Use dialog X or Cancel on a meaningful create. Discard confirmation appears. Declining preserves everything; confirming removes the lifecycle.
- WT-05 — Pinned create handoff: Pin a create, save it, and observe one pinned saved preview in exactly the create item's former position.
- WT-06 — Unpinned create completion: Save an unpinned create explicitly. Its working item closes by default and no duplicate saved preview appears.
- WT-07 — Stable project/note order: Pin and save a project. Add a note afterward. The note is before the project. Resume the project; the order does not change.
- WT-08 — Existing item activation: Open the same saved record from a route, list, search result, and its working tab. There is one lifecycle and it never moves.
- WT-09 — Remove and re-add: Remove a saved record with the topbar X. Open it again. It is a new rail addition and appears first without deleting the record.
- WT-10 — Preview/edit continuity: Open a preview, enter Edit, hide it, navigate, and resume. The exact edit draft returns in the original rail position.
- WT-11 — Pin during edit: Pin an existing edit, save, and return to preview. The lifecycle remains pinned and stationary.
- WT-12 — Unpin without close: Unpin an active item. It remains open and stationary. Hiding it applies normal engagement retention rules.
- WT-13 — Nested independent create: Start a note from a retained timer or project. The note is inserted first; the parent stays in its original position and resumes unchanged afterward.
- WT-14 — Parent-owned substage: Open an operation belonging to a saved record. No second unrelated tab appears, and the parent does not move.
- WT-15 — Failed save: Force a validation or server error. Draft, pin, stage, visibility, and position remain unchanged.
- WT-16 — Topbar X on saved record: Remove a pinned saved item. The shortcut disappears only after any required confirmation; the underlying record remains.
- WT-17 — Autosaved engagement: Type, allow autosave to clear dirty state, then hide. The engaged item remains.
- WT-18 — Organization isolation: Switch organizations or users. No label, draft, pin, or preview from the previous scope appears.
- WT-19 — Refresh recovery: Refresh with an engaged or pinned draft. It returns in the promised scope and stable order.
- WT-20 — No silent eviction: Fill any working-item limit with retained work. The app never silently removes pinned, dirty, engaged, or active items.
Feature compliance checklist
For every Core row in the lifecycle audit, implementation is incomplete until the applicable checks pass:
The shared default implementation now satisfies the lifecycle-level checks below. Individual feature-owned draft codecs retain their feature-specific validation and recovery responsibilities.
- new lifecycle opens immediately and is inserted first
- existing lifecycle activation does not reorder it
- stable added order survives stage changes and handoff
- untouched unpinned hide removes the lifecycle
- Pin retains an untouched lifecycle immediately
- meaningful unpinned hide retains the exact draft
- autosave preserves engagement
- pointer outside, Minus, and Escape are non-destructive
- dialog X and Cancel use the correct discard semantics
- topbar X removes working state without deleting saved data
- pinned create save hands off atomically
- preview and edit deduplicate to one saved lifecycle
- navigation and refresh preserve the promised persistence tier
- nested work preserves its parent identity
- save failure is lossless
- organization, user, and authorization scope are enforced
- keyboard and screen-reader behavior is covered
Implementation readiness requirements
Implementation should not begin as isolated per-dialog patches. The shared design must first provide:
- one lifecycle/event model for ordering, retention, pinning, handoff, and removal
- one immutable cross-owner added-order mechanism
- one adapter contract for create, preview, edit, quick-note, timer, and parent-stage renderers
- one canonical identity and deduplication path for saved records
- versioned migration from existing create, quick-note, temporary-tab, edit-tab, and durable-pin stores without losing valid work
- feature-by-feature tests derived from the audit and acceptance scenarios
Implementation details may evolve, but they may not weaken the behavior in this specification.
Explicit non-goals
- The rail is not browser history or a list of most recently viewed records.
- Activating an item does not move it to the front.
- Pinning is not a favorite/star system and does not change feature-level starred state.
- Transient confirmations and security prompts are not resumable work.
- Unsaved drafts are not promised across devices without a dedicated backend draft system.
- Removing a working tab never means deleting its saved record.