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,302 @@
# 2.1 — Editor
**Next Step:** → [Results Panel](../2.2-results-panel/2.2-results-panel.md)
---
## Page Metadata
| Property | Value |
|----------|-------|
| **Scenario** | 02 — Calculation Experience |
| **Page Number** | 2.1 |
| **Platform** | Web (PWA), portable to macOS/Windows |
| **Page Type** | Embedded Panel (within App Shell main area) |
| **Viewport** | All breakpoints |
| **Interaction** | Keyboard-primary, mouse secondary |
---
## Overview
**Page Purpose:** The editor is where calculations happen. Users type natural-language math expressions, define variables, add comments, and organize their thinking. It must feel like a fast, responsive text editor — not a calculator widget.
**User Situation:** User is actively thinking through numbers. They're writing a budget, converting units, figuring out a mortgage, or doing quick math. The editor must never get in their way — every keystroke should feel instant.
**Success Criteria:**
- Typing latency < 16ms (60fps)
- Results update within 50ms of pause (debounce)
- Syntax highlighting aids comprehension without distraction
- Errors are visible but non-intrusive
- Visual hierarchy makes 50-line documents scannable
**Entry Points:**
- Opening a document (tab click, sidebar double-click, new document)
- Returning to active document
**Exit Points:**
- Switching tabs (editor content swaps)
- Closing document
---
## Reference Materials
**Existing Implementation:**
- `calcpad-web/src/editor/CalcEditor.tsx` Current CodeMirror wrapper
- `calcpad-web/src/editor/calcpad-language.ts` Syntax highlighting
- `calcpad-web/src/editor/error-display.ts` Error underlines + gutter
- `calcpad-web/src/editor/inline-results.ts` Zebra striping
**Design System:**
- [Spacing Scale](../../../D-Design-System/00-design-system.md#spacing-scale)
- [Type Scale](../../../D-Design-System/00-design-system.md#type-scale)
---
## Layout Structure
```
┌──────────────────────────────────────────┐
│ [Gutter] [Line Content] │
│ │
│ 1 │ # Monthly Budget │ ← heading (bold, larger)
│ 2 │ │ ← empty line
│ 3 │ // Income │ ← comment (muted)
│ 4 │ salary = 5000 │ ← variable assignment
│ 5 │ freelance = 1200 │ ← variable assignment
│ 6 │ total_income = salary + freelance │ ← expression
│ 7 │ │
│ 8 │ // Expenses │ ← comment
│ 9 │ rent = 1500 │
│ 10 │ groceries = 400 │
│ 11 │ utilities = rent * 5% ̰ ̰ ̰ ̰ │ ← error underline
│ 12 │ │
│ 13 │ total_expenses = sum │ ← aggregator
│ 14 │ savings = total_income - total_exp │
│ │ │
│ │ │
└──────────────────────────────────────────┘
Zebra striping on alternating lines
```
---
## Spacing
| Property | Token | Pixels |
|----------|-------|--------|
| Content padding top/bottom | space-sm | 8px |
| Line padding horizontal | space-md | 12px |
| Gutter width | | 40px (auto-expands for > 999 lines) |
| Gutter padding right | space-xs | 6px |
| Line height | — | 24px (15px font × 1.6) |
| Error gutter width | — | 20px |
**Change from current:** Reduced line padding from 16px → 12px to match macOS's tighter feel.
---
## Typography
| Element | Size | Weight | Typeface | Color |
|---------|------|--------|----------|-------|
| Line content | text-md (15px) | 400 | system mono | var(--text) |
| Headings (# lines) | text-md (15px) | 700 | system mono | var(--text-h) |
| Comments (// lines) | text-md (15px) | 400 italic | system mono | var(--text) at 50% opacity |
| Variable names | text-md (15px) | 400 | system mono | var(--syntax-variable) |
| Numbers | text-md (15px) | 400 | system mono | var(--syntax-number) |
| Operators | text-md (15px) | 400 | system mono | var(--syntax-operator) |
| Keywords | text-md (15px) | 500 | system mono | var(--syntax-keyword) |
| Functions | text-md (15px) | 400 | system mono | var(--syntax-function) |
| Currency symbols | text-md (15px) | 400 | system mono | var(--syntax-currency) |
| Line numbers (gutter) | text-xs (13px) | 400 | system mono | var(--text) at 40% opacity |
| Active line number | text-xs (13px) | 600 | system mono | var(--text) |
---
## Syntax Highlighting Tokens
New CSS custom properties for syntax colors, per-theme:
| Token | Light | Dark | Matrix |
|-------|-------|------|--------|
| --syntax-variable | #4f46e5 (indigo-600) | #a5b4fc (indigo-300) | #00ff41 |
| --syntax-number | #0d9488 (teal-600) | #5eead4 (teal-300) | #00cc33 |
| --syntax-operator | #6b6375 (text) | #9ca3af (text) | #00ff41 |
| --syntax-keyword | #7c3aed (violet-600) | #c4b5fd (violet-300) | #39ff14 |
| --syntax-function | #2563eb (blue-600) | #93c5fd (blue-300) | #00ff41 |
| --syntax-currency | #d97706 (amber-600) | #fcd34d (amber-300) | #ffff00 |
| --syntax-comment | rgba(text, 0.5) | rgba(text, 0.5) | rgba(#00ff41, 0.4) |
| --syntax-heading | var(--text-h) | var(--text-h) | #00ff41 |
| --syntax-error | #e53e3e | #fc8181 | #ff0000 |
---
## Visual Hierarchy Improvements
### Headings (`# lines`)
| Property | Value |
|----------|-------|
| Font weight | 700 (bold) |
| Color | var(--text-h) — strongest text color |
| Top margin | 8px extra (visual section break) — only if preceded by non-empty line |
| Bottom margin | 0 (heading belongs to content below) |
| Background | None (clean) |
### Comments (`// lines`)
| Property | Value |
|----------|-------|
| Font style | Italic |
| Opacity | 50% of text color |
| No background stripe | Comments skip zebra striping (visually distinct already) |
### Empty Lines
| Property | Value |
|----------|-------|
| Height | 24px (same as content lines) |
| Background | Normal zebra stripe pattern |
| Purpose | Visual breathing room, section separators |
### Active Line
| Property | Value |
|----------|-------|
| Background | var(--accent-bg) — subtle accent tint |
| Gutter | Line number bold + full opacity |
| Transition | background 0.1s |
---
## Error Display
### Error Underline
**OBJECT ID:** `editor-error-underline`
| Property | Value |
|----------|-------|
| Style | wavy underline |
| Color | var(--syntax-error) |
| Thickness | 1.5px |
| Scope | Underlines the specific token/expression that errored |
### Error Gutter Marker
**OBJECT ID:** `editor-error-gutter`
| Property | Value |
|----------|-------|
| Icon | ⚠ (warning triangle) |
| Size | 14px |
| Color | var(--syntax-error) |
| Position | Error gutter column (20px wide, left of line numbers) |
| Hover | Tooltip with error message text |
### Error Tooltip
**OBJECT ID:** `editor-error-tooltip`
| Property | Value |
|----------|-------|
| Trigger | Hover over error gutter marker OR underlined text |
| Background | var(--bg-secondary) |
| Border | 1px solid var(--border) |
| Border-radius | 4px |
| Padding | 4px 8px |
| Font | text-xs, system sans, var(--syntax-error) |
| Shadow | 0 2px 8px rgba(0,0,0,0.15) |
| Max width | 300px |
| Position | Below the error line, left-aligned with gutter |
---
## Zebra Striping
| Property | Value |
|----------|-------|
| Pattern | Even-numbered lines (matching current implementation) |
| Light mode | rgba(0, 0, 0, 0.02) — reduced from 0.025 |
| Dark mode | rgba(255, 255, 255, 0.025) — reduced from 0.035 |
| Matrix mode | rgba(0, 255, 65, 0.03) — green tint |
| Skip on | Comment lines (already visually distinct) |
---
## Autocomplete
**OBJECT ID:** `editor-autocomplete`
| Property | Value |
|----------|-------|
| Trigger | Typing 2+ characters that match a variable, function, or keyword |
| Panel | Dropdown below cursor, var(--bg) bg, 1px border, 4px radius |
| Max items | 8 visible, scroll for more |
| Item height | 28px |
| Active item | var(--accent-bg) highlight |
| Categories | Variables (with last value), Functions (with signature), Keywords, Units, Currencies |
| Keyboard | ↑↓ to navigate, Tab/Enter to accept, Esc to dismiss |
| Auto-dismiss | On cursor movement away |
---
## Page States
| State | When | Behavior |
|-------|------|----------|
| **Active editing** | User is typing | Debounced eval (50ms). Results update. Auto-save (500ms). |
| **Idle** | User paused | All results current. Document saved. |
| **Read-only** | Template preview (future) | No cursor, no editing. Gray overlay on gutter. |
| **Engine loading** | WASM initializing | Editor is fully editable. Results show "—" until engine ready. |
| **Large document** | > 500 lines | Viewport rendering only (CodeMirror handles this). Performance warning in status bar if > 1000 lines. |
---
## Interactions
| Action | Behavior |
|--------|----------|
| Type expression | Debounced eval → results update in 50ms |
| Define variable (`x = 5`) | Variable registered, available for autocomplete on subsequent lines |
| Reference variable | Autocomplete suggests matching variables with their current values |
| Use aggregator (`sum`, `total`) | Aggregates all numeric results above (up to previous heading or empty line block) |
| Line reference (`#3`) | References result of line 3 |
| Comment (`// text`) | Line excluded from evaluation, styled as comment |
| Heading (`# text`) | Section header, not evaluated, used for visual grouping and aggregator scoping |
| Select text | Selection count shown in status bar |
| Cmd/Ctrl+Z | Undo (per-document history preserved while tab is open) |
| Cmd/Ctrl+Shift+Z | Redo |
| Cmd/Ctrl+D | Duplicate current line |
| Cmd/Ctrl+/ | Toggle comment on current line |
| Alt+↑/↓ | Move line up/down |
---
## Technical Notes
- **CodeMirror instance management:** One instance per active tab. Inactive tabs store EditorState (preserves undo history). On tab switch, create new EditorView with stored state.
- **Eval debounce:** 50ms (current). Consider making configurable in settings (0200ms range).
- **Syntax highlighting performance:** StreamLanguage parser runs synchronously — fine for < 1000 lines. For very large documents, consider switching to Lezer grammar.
- **Font scaling:** Expose font size setting (1224px range) in settings. Current 15px is good default. Store in localStorage.
- **Cross-platform:** CodeMirror handles keyboard differences (Cmd vs Ctrl). Same extension stack works everywhere via WASM.
---
## Open Questions
| # | Question | Context | Status |
|---|----------|---------|--------|
| 1 | Should headings have extra top margin? | Creates visual sections but breaks 1:1 line alignment with results panel | 🟡 In Discussion likely yes, results panel adjusts |
| 2 | Should comments skip zebra striping? | Makes them more visually distinct but breaks the pattern | 🔴 Open |
| 3 | Font size as user preference? | 1224px range slider in settings | 🟢 Resolved: Yes, default 15px |
---
**Next Step:** [Results Panel](../2.2-results-panel/2.2-results-panel.md)
---
_Created using Whiteport Design Studio (WDS) methodology_