feat(web): implement complete workspace with themes, tabs, sidebar, and mobile

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>
This commit is contained in:
2026-03-18 09:12:05 -04:00
parent 806e2f1ec6
commit 0d38bd3108
78 changed files with 8175 additions and 421 deletions

View File

@@ -0,0 +1,257 @@
# 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+19** | 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_