Transform CalcText from a single-document calculator into a full workspace application with multi-document support, theming, and responsive mobile experience. - Theme system: 5 presets (Light, Dark, Matrix, Midnight, Warm) + accent colors - Document model with localStorage persistence and auto-save - Tab bar with keyboard shortcuts (Ctrl+N/W/Tab/1-9), rename, close - Sidebar with search, recent, favorites, folders, templates, drag-and-drop - 5 templates: Budget, Invoice, Unit Converter, Trip Planner, Loan Calculator - Status bar with cursor position, engine status, dedication to Igor Cassel - Results panel: type-specific colors, click-to-copy, error hints - Format toolbar: H, B, I, //, color labels with live preview toggle - Syntax highlighting using theme CSS variables - Error hover tooltips - Mobile: bottom results tray, sidebar drawer, touch targets, safe areas - Docker multi-stage build (Rust WASM + Vite + Nginx) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
258 lines
8.2 KiB
Markdown
258 lines
8.2 KiB
Markdown
# 3.1 — Tab Bar & Document Lifecycle
|
||
|
||
**Next Step:** → [Sidebar](../../04-file-organization/4.1-sidebar/4.1-sidebar.md)
|
||
|
||
---
|
||
|
||
## Page Metadata
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| **Scenario** | 03 — Document Management |
|
||
| **Page Number** | 3.1 |
|
||
| **Platform** | Web (PWA), portable to macOS/Windows |
|
||
| **Page Type** | Embedded Strip (within App Shell, between header and editor) |
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
**Page Purpose:** Enable multi-document workflow. Users can open, create, close, reorder, and rename documents via tabs. The tab bar is the primary navigation between open documents.
|
||
|
||
**Success Criteria:**
|
||
- Switching tabs feels instant (< 50ms)
|
||
- Users never lose work (auto-save before switch)
|
||
- Tab state survives page reload
|
||
- 15+ tabs remain usable (horizontal scroll)
|
||
|
||
---
|
||
|
||
## Layout Structure
|
||
|
||
```
|
||
┌──────────────────────────────────────────────────────────┐
|
||
│ [● Budget ×] [ Invoice ×] [ Unit Conv ×] [+] ··· │ 36px
|
||
└──────────────────────────────────────────────────────────┘
|
||
● = unsaved changes dot
|
||
Active tab: connected to editor (no bottom border)
|
||
[+] = new document button
|
||
··· = overflow gradient when scrollable
|
||
```
|
||
|
||
---
|
||
|
||
## Spacing
|
||
|
||
| Property | Token | Pixels |
|
||
|----------|-------|--------|
|
||
| Tab bar height | — | 36px |
|
||
| Tab padding horizontal | space-sm | 8px |
|
||
| Tab gap | — | 0px (tabs are flush, separated by 1px border) |
|
||
| Tab min width | — | 100px |
|
||
| Tab max width | — | 200px |
|
||
| Close button size | — | 16px × 16px |
|
||
| Close button margin-left | space-xs | 6px |
|
||
| Modified dot size | — | 6px |
|
||
| Modified dot margin-right | space-2xs | 4px |
|
||
| New tab button width | — | 36px (square) |
|
||
|
||
---
|
||
|
||
## Typography
|
||
|
||
| Element | Size | Weight | Color |
|
||
|---------|------|--------|-------|
|
||
| Tab label (active) | text-xs | 500 | var(--text-h) |
|
||
| Tab label (inactive) | text-xs | 400 | var(--text) |
|
||
| Close × | text-xs | 400 | var(--text) at 50%, hover → var(--text) |
|
||
| New + icon | text-sm | 300 | var(--text), hover → var(--accent) |
|
||
|
||
---
|
||
|
||
## Tab States
|
||
|
||
### Active Tab
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Background | var(--bg) — same as editor, creates visual connection |
|
||
| Border bottom | None — tab "opens into" the editor |
|
||
| Border left/right | 1px solid var(--border) |
|
||
| Border top | 2px solid var(--accent) — active indicator |
|
||
| Label | Weight 500, var(--text-h) |
|
||
| Close button | Always visible |
|
||
|
||
### Inactive Tab
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Background | var(--bg-secondary) |
|
||
| Border bottom | 1px solid var(--border) — closed off from editor |
|
||
| Border left/right | 1px solid var(--border) |
|
||
| Border top | 2px solid transparent |
|
||
| Label | Weight 400, var(--text) |
|
||
| Close button | Visible on hover only |
|
||
|
||
### Hover (Inactive)
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Background | Blend between bg-secondary and bg (subtle lighten) |
|
||
| Transition | background 0.1s |
|
||
|
||
### Dragging
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Appearance | Tab lifts with subtle shadow (0 2px 8px rgba(0,0,0,0.15)) |
|
||
| Opacity | 90% |
|
||
| Placeholder | 2px var(--accent) vertical line at insertion point |
|
||
| Cursor | grabbing |
|
||
|
||
---
|
||
|
||
## Interactions
|
||
|
||
| Action | Behavior |
|
||
|--------|----------|
|
||
| **Click tab** | Switch to document. Auto-save current. Restore editor state (content, cursor, scroll, undo). |
|
||
| **Click +** | Create "Untitled" doc, open in new tab, focus editor. |
|
||
| **Click ×** | Close tab. If unsaved and modified, no prompt (auto-saved). Remove from openTabIds. |
|
||
| **Middle-click tab** | Close tab (same as ×). |
|
||
| **Double-click tab** | Inline rename — label becomes input field, Enter confirms, Esc cancels. |
|
||
| **Drag tab** | Reorder. Drop position shown by accent line indicator. |
|
||
| **Ctrl/Cmd+Tab** | Next tab (wraps). |
|
||
| **Ctrl/Cmd+Shift+Tab** | Previous tab (wraps). |
|
||
| **Ctrl/Cmd+W** | Close active tab. If last tab, create new "Untitled". |
|
||
| **Ctrl/Cmd+N** | New document + tab. |
|
||
| **Ctrl/Cmd+1–9** | Jump to tab by position. |
|
||
| **Mouse wheel on tab bar** | Horizontal scroll when tabs overflow. |
|
||
|
||
---
|
||
|
||
## Tab Overflow
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Trigger | Total tab width > container width |
|
||
| Scroll | Horizontal, smooth, via mouse wheel or trackpad |
|
||
| Indicators | 16px fade gradient on left/right edges when scrollable |
|
||
| Active tab | Auto-scrolls into view when selected via keyboard |
|
||
| New tab button | Sticky right — always visible outside scroll area |
|
||
|
||
---
|
||
|
||
## Document Lifecycle
|
||
|
||
### Create
|
||
|
||
| Trigger | Behavior |
|
||
|---------|----------|
|
||
| Click [+] | New doc: `{ id: uuid(), title: "Untitled", content: "", folderId: null }` |
|
||
| Sidebar template click | New doc with template content and suggested title |
|
||
| Ctrl/Cmd+N | Same as [+] |
|
||
|
||
Title auto-increments: "Untitled", "Untitled 2", "Untitled 3"...
|
||
|
||
### Rename
|
||
|
||
| Trigger | Behavior |
|
||
|---------|----------|
|
||
| Double-click tab | Label becomes `<input>`, pre-selected text, 200px max width |
|
||
| Sidebar right-click → Rename | Same inline editing in sidebar |
|
||
| Enter | Confirm rename, update document and sidebar |
|
||
| Esc | Cancel, revert to previous name |
|
||
| Blur | Confirm (same as Enter) |
|
||
| Empty name | Revert to "Untitled" |
|
||
|
||
### Delete
|
||
|
||
| Trigger | Behavior |
|
||
|---------|----------|
|
||
| Sidebar right-click → Delete | Confirmation: "Delete '{title}'? This cannot be undone." |
|
||
| Confirm | Remove from documents[], close tab if open, remove from folder |
|
||
| Cancel | No action |
|
||
| Undo | 5-second toast: "Document deleted. [Undo]" — restores document if clicked |
|
||
|
||
### Duplicate
|
||
|
||
| Trigger | Behavior |
|
||
|---------|----------|
|
||
| Sidebar right-click → Duplicate | New doc: same content, title = "{original} (copy)", same folder |
|
||
| Opens in new tab automatically |
|
||
|
||
### Auto-Save
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Trigger | Document content change |
|
||
| Debounce | 500ms after last keystroke |
|
||
| Storage | localStorage (calctext-documents) |
|
||
| Indicator | Modified dot (●) appears immediately on change, disappears on save |
|
||
| Manual save | Ctrl/Cmd+S shows brief checkmark animation in tab (visual confirmation) |
|
||
|
||
---
|
||
|
||
## Modified Indicator
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Shape | Filled circle, 6px |
|
||
| Color | var(--text) at 60% |
|
||
| Position | Before tab label, 4px gap |
|
||
| Appears | On first character change |
|
||
| Disappears | On auto-save completion (500ms debounce) |
|
||
| Animation | Fade in 0.2s |
|
||
|
||
---
|
||
|
||
## Context Menu (Right-Click Tab)
|
||
|
||
| Item | Action |
|
||
|------|--------|
|
||
| Close | Close this tab |
|
||
| Close Others | Close all tabs except this one |
|
||
| Close to the Right | Close all tabs to the right |
|
||
| — | (separator) |
|
||
| Rename | Inline rename |
|
||
| Duplicate | Duplicate document |
|
||
| — | (separator) |
|
||
| Reveal in Sidebar | Scroll sidebar to show this file |
|
||
|
||
---
|
||
|
||
## Mobile Adaptations
|
||
|
||
| Property | Value |
|
||
|----------|-------|
|
||
| Tab bar | Horizontal scroll, touch-friendly |
|
||
| Tab height | 40px (larger touch target) |
|
||
| Close button | Hidden — swipe left on tab to reveal close |
|
||
| New tab | [+] button at far right |
|
||
| Rename | Long-press → context menu → Rename |
|
||
| Reorder | Long-press + drag |
|
||
|
||
---
|
||
|
||
## Page States
|
||
|
||
| State | When | Behavior |
|
||
|-------|------|----------|
|
||
| **Single tab** | Only one document open | Tab still shown (establishes pattern). Close creates new "Untitled". |
|
||
| **Many tabs (>10)** | Heavy usage | Horizontal scroll. Active tab auto-scrolls into view. |
|
||
| **All tabs closed** | User closed everything | Auto-create "Untitled" tab (workspace never empty). |
|
||
| **First launch** | No localStorage | Single "Welcome" tab with demo content. |
|
||
|
||
---
|
||
|
||
## Technical Notes
|
||
|
||
- **Tab switching performance:** Store `EditorState` per tab in memory. On switch: destroy current EditorView, create new with stored state. Content swap < 20ms.
|
||
- **Tab order persistence:** `openTabIds: string[]` in localStorage maintains order.
|
||
- **Cross-platform:** Maps to native tab bars. macOS: NSTabView or custom tab strip. Windows: iced tabs widget.
|
||
- **Max tabs:** Soft limit at 20 with performance hint in status bar. No hard limit.
|
||
|
||
---
|
||
|
||
_Created using Whiteport Design Studio (WDS) methodology_
|