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:
@@ -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 (0–200ms 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 (12–24px 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? | 12–24px 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_
|
||||
@@ -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_
|
||||
Reference in New Issue
Block a user