Files
C. Cassel 0d38bd3108 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>
2026-03-18 09:12:05 -04:00

303 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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_