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.2 — Results Panel
**Previous Step:** ← [Editor](../2.1-editor/2.1-editor.md)
---
## Page Metadata
| Property | Value |
|----------|-------|
| **Scenario** | 02 — Calculation Experience |
| **Page Number** | 2.2 |
| **Platform** | Web (PWA), portable to macOS/Windows |
| **Page Type** | Embedded Panel (within App Shell main area) |
| **Viewport** | Desktop + tablet (side panel), mobile (bottom tray) |
---
## Overview
**Page Purpose:** Display calculation results aligned line-by-line with the editor. Results are the reason the product exists — they must be clear, informative, and subtly styled so they complement the editor without competing for attention.
**User Situation:** User is typing in the editor and their eyes flick right to see results. This happens dozens of times per session. The results must be instantly scannable — the eye should find the answer in milliseconds.
**Success Criteria:**
- Every result line aligns pixel-perfectly with its editor line
- Results are readable but visually secondary to the editor
- Different result types are distinguishable at a glance
- Scroll synchronization is seamless
**Entry Points:**
- Always visible on desktop/tablet (side panel)
- Toggle on mobile (bottom tray)
---
## Layout Structure
### Desktop/Tablet (side panel)
```
┌──────────────────────────┐
│ │
│ ──── │ ← heading (no result)
│ │ ← empty
│ ──── │ ← comment (no result)
│ 5,000 │ ← number
│ 1,200 │ ← number
│ 6,200 │ ← expression result
│ │ ← empty
│ ──── │ ← comment
│ 1,500 │ ← number
│ 400 │ ← number
│ ⚠ error │ ← error (muted, not red)
│ │ ← empty
│ 1,900 │ ← aggregator result
│ 4,300 │ ← expression result
│ │
└──────────────────────────┘
Right-aligned, zebra striping matches editor
```
### Mobile (bottom tray)
```
┌─────────────────────────────┐
│ ═══ (drag handle) │ 48px collapsed
│ Last result: 4,300 │
├─────────────────────────────┤
│ Ln 5: salary = 5000 │ ← expanded: shows
│ Ln 6: freelance = 1200 │ line + result pairs
│ Ln 7: total_income = 6200 │ scrollable
│ ... │
└─────────────────────────────┘
```
---
## Spacing
| Property | Token | Pixels |
|----------|-------|--------|
| Content padding top/bottom | space-sm | 8px (matches editor) |
| Result line padding horizontal | space-md | 12px (matches editor) |
| Result line height | — | 24px (matches editor exactly) |
| Mobile tray collapsed height | — | 48px |
| Mobile tray expanded height | — | 40vh |
| Mobile drag handle | — | 32px × 4px pill |
---
## Typography
| Element | Size | Weight | Typeface | Color |
|---------|------|--------|----------|-------|
| Numeric result | text-md (15px) | 400 | system mono | var(--result-number) |
| Unit value | text-md (15px) | 400 | system mono | var(--result-unit) |
| Currency value | text-md (15px) | 400 | system mono | var(--result-currency) |
| DateTime result | text-md (15px) | 400 | system mono | var(--result-datetime) |
| Boolean result | text-md (15px) | 400 | system mono | var(--result-boolean) |
| Error hint | text-xs (13px) | 400 | system sans | var(--text) at 30% opacity |
| Non-result lines | — | — | — | Empty (no text rendered) |
**Design change from current:** Weight reduced from 600 → 400. Color changed from var(--accent) to type-specific semantic colors. This makes results secondary to the editor content, matching macOS behavior.
---
## Result Type Colors
New CSS custom properties per theme:
| Token | Light | Dark | Matrix | Purpose |
|-------|-------|------|--------|---------|
| --result-number | #374151 (gray-700) | #d1d5db (gray-300) | #00ff41 | Plain numbers |
| --result-unit | #0d9488 (teal-600) | #5eead4 (teal-300) | #00cc33 | Values with units (5 kg, 3.2 m) |
| --result-currency | #d97706 (amber-600) | #fcd34d (amber-300) | #ffff00 | Currency values ($50, €42) |
| --result-datetime | #7c3aed (violet-600) | #c4b5fd (violet-300) | #39ff14 | Dates and times |
| --result-boolean | #6366f1 (indigo-500) | #818cf8 (indigo-400) | #00ff41 | true/false |
| --result-error | var(--text) at 30% | var(--text) at 30% | rgba(#ff0000, 0.3) | Error hint text |
**Why type-specific colors:** Users scanning results can instantly distinguish "that's a currency" from "that's a unit" from "that's a date" without reading the value. The colors are muted (not saturated) so they don't compete with the editor.
---
## Result Display Format
| Result Type | Display Format | Example |
|-------------|----------------|---------|
| Number | Formatted with thousand separators | `6,200` |
| Unit value | Value + unit abbreviation | `2.2 kg` · `156.2 mi` |
| Currency | Symbol + value | `$4,300` · `€3,857.20` |
| DateTime | Locale-formatted | `Mar 25, 2026` · `14:30` |
| TimeDelta | Human-readable | `3 days, 4 hours` |
| Boolean | Lowercase | `true` · `false` |
| Comment | Empty line (dash marker) | `────` (subtle horizontal line) |
| Heading | Empty line (dash marker) | `────` |
| Empty | Empty line | (blank) |
| Error | Muted hint | `· error` (tiny, de-emphasized) |
| Variable assignment | Show assigned value | `5,000` |
**Change from current:** Removed the `= ` prefix before results. Just show the value. Cleaner.
### Comment/Heading Marker
| Property | Value |
|----------|-------|
| Content | `────` (4 em dashes, or CSS border-bottom) |
| Color | var(--border) at 50% opacity |
| Purpose | Visual separator showing this line has no numeric result |
| Width | 60% of panel width, right-aligned |
### Error Hint
| Property | Value |
|----------|-------|
| Content | `· error` or `· invalid` |
| Color | var(--text) at 30% opacity |
| Purpose | Subtle indicator that something went wrong — details are in the editor gutter |
| Font | text-xs (13px), system sans |
---
## Zebra Striping
| Property | Value |
|----------|-------|
| Pattern | Matches editor exactly (same even-line pattern) |
| Colors | Same as editor per theme |
| Sync | Uses line index, not DOM position, for consistency |
---
## Scroll Synchronization
| Property | Value |
|----------|-------|
| Direction | Editor drives, results follows |
| Method | `scrollTop` mirroring via passive scroll listener |
| Latency | < 1 frame (requestAnimationFrame if needed) |
| Results panel | `overflow-y: hidden` no independent scrolling |
| Edge case | If editor has heading margins (extra space), results panel inserts matching spacers |
---
## Result Hover Interaction
**OBJECT ID:** `results-hover`
| Property | Value |
|----------|-------|
| Trigger | Mouse hover over a result line |
| Behavior | Show full precision + unit details in tooltip |
| Tooltip content | Raw value (full precision), type label, conversion hint |
| Example | Hover `$4,300` "4300.00 USD (United States Dollar)" |
| Example | Hover `2.2 kg` "2.20462 kg (kilogram) · 4.85 lb" |
| Style | Same as error tooltip (bg-secondary, border, 4px radius) |
| Delay | 500ms hover delay (don't trigger on casual mouse movement) |
---
## Result Click Interaction
**OBJECT ID:** `results-click`
| Property | Value |
|----------|-------|
| Trigger | Single click on a result value |
| Behavior | Copy raw value to clipboard |
| Feedback | Brief flash (0.3s) result text turns var(--success) then fades back |
| Tooltip | "Copied!" appears for 1.5s |
| Accessibility | aria-label="Copy result: {value}" |
---
## Mobile Bottom Tray
**OBJECT ID:** `results-mobile-tray`
### Collapsed State (48px)
| Property | Value |
|----------|-------|
| Content | Drag handle + last non-empty result value |
| Drag handle | 32px × 4px pill, var(--border), centered |
| Result text | "Last: {value}" or "No results" if empty |
| Font | text-xs, var(--text) |
| Background | var(--bg-secondary) |
| Border | top 1px solid var(--border) |
| Interaction | Tap or swipe up expand |
### Expanded State (40vh)
| Property | Value |
|----------|-------|
| Content | Scrollable list of all line results paired with their expressions |
| Item format | `Ln {n}: {expression} → {result}` |
| Item height | 36px (larger for touch) |
| Active line | Highlighted with var(--accent-bg) |
| Tap result | Copy to clipboard (same as desktop click) |
| Interaction | Swipe down collapse. Tap backdrop collapse. |
| Scroll | Independent scroll (not synced with editor in mobile) |
### Transitions
| Property | Value |
|----------|-------|
| Expand/collapse | 200ms ease-out |
| Spring | Optional subtle overshoot on expand |
---
## Page States
| State | When | Results Display |
|-------|------|-----------------|
| **Normal** | Engine ready, results computed | Type-colored values per line |
| **Engine loading** | WASM initializing | All result lines show `—` in var(--text) at 20% |
| **Empty document** | No lines in editor | Panel is blank |
| **All errors** | Every line has errors | All lines show muted `· error` hints |
| **Stale results** | Document changed, eval pending | Previous results stay visible (no flash/flicker) |
---
## Accessibility
| Feature | Implementation |
|---------|---------------|
| ARIA | `role="complementary"`, `aria-label="Calculation results"` |
| Live updates | `aria-live="polite"` on result container announces new results |
| Screen reader | Each result: `aria-label="Line {n}: {expression} equals {result}"` |
| Color-blind | Result types distinguishable by position + format, not just color |
| Click feedback | `aria-label="Copied"` announced on clipboard copy |
| Reduced motion | No flash animation; instant color change for copy feedback |
---
## Technical Notes
- **Result alignment:** Line height must match editor exactly (24px). If editor adds heading margins, results panel must add matching spacer divs.
- **Rendering:** ResultsPanel receives `EngineLineResult[]` from useEngine hook. Re-renders only changed lines (React key by line index).
- **Copy to clipboard:** Use `navigator.clipboard.writeText()`. Fall back to textarea trick for older browsers.
- **Hover tooltip positioning:** Position below the result line, right-aligned with panel. Flip above if near viewport bottom.
- **Mobile tray:** Use CSS `transform: translateY()` for smooth expand/collapse. Touch events for swipe gesture.
- **Cross-platform:** Side panel native split view (macOS/Windows). Mobile tray not applicable on desktop native.
---
## Open Questions
| # | Question | Context | Status |
|---|----------|---------|--------|
| 1 | Should heading lines in results show `────` markers? | Helps visual alignment but adds visual noise | 🔴 Open |
| 2 | Should copy-on-click copy formatted or raw value? | `$4,300` vs `4300` | 🟡 Likely raw (more useful for pasting) |
| 3 | Result hover tooltip show conversion alternatives? | "2.2 kg · 4.85 lb" on hover | 🟢 Resolved: Yes, useful for unit/currency results |
---
**Previous Step:** [Editor](../2.1-editor/2.1-editor.md)
---
_Created using Whiteport Design Studio (WDS) methodology_