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:
42
_bmad-output/C-UX-Scenarios/00-ux-scenarios.md
Normal file
42
_bmad-output/C-UX-Scenarios/00-ux-scenarios.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# UX Scenarios: CalcText
|
||||
|
||||
> Design experiences, not screens — every page serves a user with a goal and an emotion.
|
||||
|
||||
**Created:** 2026-03-17
|
||||
**Phase:** 3 (Scenario Outline) + Phase 4 (UX Design)
|
||||
**Agents:** Freya (Page Specifications)
|
||||
|
||||
---
|
||||
|
||||
## Scenarios
|
||||
|
||||
| # | Scenario | Pages | Description |
|
||||
|---|----------|-------|-------------|
|
||||
| 01 | Workspace Shell | App Shell, Status Bar | The overall 3-panel layout that contains everything |
|
||||
| 02 | Calculation Experience | Editor, Results Panel | The core — typing calculations, seeing results |
|
||||
| 03 | Document Management | Tab Bar, New Document, Rename/Delete | Multi-document workflow |
|
||||
| 04 | File Organization | Sidebar, Folder Tree, Templates, Recent/Favorites | Organizing calctext files |
|
||||
| 05 | Theming | Theme Picker, Preset Themes, Custom Theme, Accent Color | Personalizing the workspace |
|
||||
| 06 | Mobile Experience | Mobile Shell, Mobile Results, Mobile Sidebar | Responsive/touch adaptation |
|
||||
|
||||
---
|
||||
|
||||
## Page Index
|
||||
|
||||
_Updated as page specifications are created during Phase 4._
|
||||
|
||||
| Scenario | Page | Status | File |
|
||||
|----------|------|--------|------|
|
||||
| 01 | App Shell | Specified | 01-workspace-shell/1.1-app-shell/1.1-app-shell.md |
|
||||
| 01 | Status Bar | Specified | 01-workspace-shell/1.2-status-bar/1.2-status-bar.md |
|
||||
| 02 | Editor | Specified | 02-calculation-experience/2.1-editor/2.1-editor.md |
|
||||
| 02 | Results Panel | Specified | 02-calculation-experience/2.2-results-panel/2.2-results-panel.md |
|
||||
| 03 | Tab Bar & Document Lifecycle | Specified | 03-document-management/3.1-tab-bar/3.1-tab-bar.md |
|
||||
| 04 | Sidebar & File Organization | Specified | 04-file-organization/4.1-sidebar/4.1-sidebar.md |
|
||||
| 04 | Templates | Specified | 04-file-organization/4.2-templates/4.2-templates.md |
|
||||
| 05 | Theme System | Specified | 05-theming/5.1-theme-system/5.1-theme-system.md |
|
||||
| 06 | Mobile Experience | Specified | 06-mobile-experience/6.1-mobile-shell/6.1-mobile-shell.md |
|
||||
|
||||
---
|
||||
|
||||
_Created using Whiteport Design Studio (WDS) methodology_
|
||||
@@ -0,0 +1,558 @@
|
||||
# 1.1 — App Shell
|
||||
|
||||
**Next Step:** → [Status Bar](../1.2-status-bar/1.2-status-bar.md)
|
||||
|
||||
---
|
||||
|
||||
## Page Metadata
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Scenario** | 01 — Workspace Shell |
|
||||
| **Page Number** | 1.1 |
|
||||
| **Platform** | Web (PWA), portable to macOS/Windows |
|
||||
| **Page Type** | Full Application Shell |
|
||||
| **Viewport** | Desktop-first, responsive to mobile |
|
||||
| **Interaction** | Mouse + keyboard (primary), touch (secondary) |
|
||||
| **Visibility** | Public (no auth required) |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Page Purpose:** The app shell is the top-level container that defines the spatial structure of the entire CalcText workspace. It arranges all panels (sidebar, editor, results), the tab bar, header, and status bar into a cohesive layout that persists across all user interactions.
|
||||
|
||||
**User Situation:** User opens CalcText in a browser. They expect a professional workspace where they can write calculations, manage files, and personalize their environment. First-time users should immediately understand the layout. Returning users should find their documents and preferences exactly as they left them.
|
||||
|
||||
**Success Criteria:**
|
||||
- User understands the 3-panel layout within 3 seconds
|
||||
- All panels are resizable and collapsible
|
||||
- Layout state persists across sessions (sidebar width, panel visibility)
|
||||
- Feels native — not like a web page
|
||||
|
||||
**Entry Points:**
|
||||
- Direct URL (calctext.app or localhost)
|
||||
- PWA launch from desktop/dock
|
||||
- Returning session (restores last state from localStorage)
|
||||
|
||||
**Exit Points:**
|
||||
- Close browser/PWA (state auto-saves)
|
||||
- All navigation is within the shell (no page transitions)
|
||||
|
||||
---
|
||||
|
||||
## Reference Materials
|
||||
|
||||
**Strategic Foundation:**
|
||||
- [Product Brief](../../../A-Product-Brief/project-brief.md) — Workspace evolution, web-first
|
||||
- [Brownfield Analysis](../../../A-Product-Brief/01-brownfield-analysis.md) — Current CSS tokens, components, gaps
|
||||
|
||||
**Related Pages:**
|
||||
- [Status Bar](../1.2-status-bar/1.2-status-bar.md) — Bottom status strip
|
||||
- Tab Bar (Scenario 03) — Document tabs above editor
|
||||
- Sidebar (Scenario 04) — File tree panel
|
||||
- Editor (Scenario 02) — Calculation editor
|
||||
- Results Panel (Scenario 02) — Results display
|
||||
|
||||
---
|
||||
|
||||
## Layout Structure
|
||||
|
||||
The app shell uses a 4-region layout: Header, Sidebar, Main Area (tabs + editor + results), and Status Bar.
|
||||
|
||||
### Desktop (>= 768px)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Header [Logo] [Theme] [Settings] [⌘] │ 40px
|
||||
├────────┬────────────────────────────────────────────────┤
|
||||
│ │ Tab Bar [Budget ×] [Invoice ×] [+] │ 36px
|
||||
│ ├─────────────────────────┬──┬──────────────────┤
|
||||
│ Side │ │ │ │
|
||||
│ bar │ Editor │ D│ Results │
|
||||
│ │ (CodeMirror 6) │ iv│ Panel │
|
||||
│ 240px │ │ │ │
|
||||
│ (coll │ │ │ │
|
||||
│ apsi │ │ │ │
|
||||
│ ble) │ │ │ │
|
||||
│ │ │ │ │
|
||||
├────────┴─────────────────────────┴──┴──────────────────┤
|
||||
│ Status Bar [Ln 12, Col 8] [Engine ●] [Dark 🎨] │ 24px
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Tablet (768px — 1024px)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ Header [Logo] [Theme] [≡] │ 40px
|
||||
├─────────────────────────────────────────┤
|
||||
│ Tab Bar [Budget ×] [Invoice ×] [+] │ 36px
|
||||
├─────────────────────────┬──┬────────────┤
|
||||
│ │ │ │
|
||||
│ Editor │ D│ Results │
|
||||
│ (CodeMirror 6) │ iv│ Panel │
|
||||
│ │ │ │
|
||||
├─────────────────────────┴──┴────────────┤
|
||||
│ Status Bar │ 24px
|
||||
└─────────────────────────────────────────┘
|
||||
Sidebar: overlay drawer via hamburger [≡]
|
||||
```
|
||||
|
||||
### Mobile (< 768px)
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ Header [Logo] [≡] │ 44px (touch)
|
||||
├─────────────────────────┤
|
||||
│ Tab Bar (scrollable) │ 36px
|
||||
├─────────────────────────┤
|
||||
│ │
|
||||
│ Editor │
|
||||
│ (full width) │
|
||||
│ │
|
||||
│ │
|
||||
├─────────────────────────┤
|
||||
│ Results Tray (toggle) │ 48px collapsed / 40vh expanded
|
||||
├─────────────────────────┤
|
||||
│ Status Bar │ 24px
|
||||
└─────────────────────────┘
|
||||
Sidebar: full-screen drawer
|
||||
Results: bottom tray with drag handle
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spacing
|
||||
|
||||
**Scale:** [Spacing Scale](../../../D-Design-System/00-design-system.md#spacing-scale)
|
||||
|
||||
| Property | Token | Pixels (proposed) |
|
||||
|----------|-------|--------------------|
|
||||
| Page padding (horizontal) | space-zero | 0px (panels fill edge-to-edge) |
|
||||
| Header padding (horizontal) | space-md | 12px |
|
||||
| Header padding (vertical) | space-xs | 6px |
|
||||
| Sidebar width (default) | — | 240px |
|
||||
| Sidebar min width | — | 180px |
|
||||
| Sidebar max width | — | 400px |
|
||||
| Divider width (sidebar ↔ editor) | — | 1px visible, 8px hit area |
|
||||
| Divider width (editor ↔ results) | — | 1px visible, 8px hit area |
|
||||
| Tab bar height | — | 36px |
|
||||
| Tab padding (horizontal) | space-sm | 8px |
|
||||
| Status bar height | — | 24px |
|
||||
| Status bar padding (horizontal) | space-md | 12px |
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
**Scale:** [Type Scale](../../../D-Design-System/00-design-system.md#type-scale)
|
||||
|
||||
| Element | Semantic | Size | Weight | Typeface |
|
||||
|---------|----------|------|--------|----------|
|
||||
| App title (header) | — | text-sm | 600 | system sans |
|
||||
| Tab label | — | text-xs | 400 (normal), 500 (active) | system sans |
|
||||
| Status bar text | — | text-3xs | 400 | system mono |
|
||||
| Sidebar section title | H3 | text-2xs | 600 | system sans |
|
||||
| Sidebar file name | — | text-xs | 400 | system sans |
|
||||
| Editor content | — | text-md | 400 | system mono |
|
||||
| Results value | — | text-md | 400 | system mono |
|
||||
|
||||
---
|
||||
|
||||
## Page Sections
|
||||
|
||||
### Section: Header
|
||||
|
||||
**OBJECT ID:** `shell-header`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Purpose | App identity, global actions, theme quick-switch |
|
||||
| Height | 40px (desktop/tablet), 44px (mobile — touch target) |
|
||||
| Background | var(--bg) |
|
||||
| Border | bottom 1px solid var(--border) |
|
||||
| Layout | Horizontal: logo-left, actions-right |
|
||||
|
||||
#### Logo Group
|
||||
|
||||
**OBJECT ID:** `shell-header-logo`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Content | CalcText icon + wordmark |
|
||||
| Icon size | 20px × 20px |
|
||||
| Wordmark | "CalcText" in text-sm, weight 600 |
|
||||
| Gap | space-xs (6px) between icon and wordmark |
|
||||
| Mobile | Wordmark hidden < 480px, icon only |
|
||||
|
||||
#### Header Actions
|
||||
|
||||
**OBJECT ID:** `shell-header-actions`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Layout | Horizontal, space-xs gap |
|
||||
| Items | Theme toggle, Settings button, Keyboard shortcuts (⌘) |
|
||||
| Button size | 28px × 28px (icon buttons) |
|
||||
| Icon size | 16px |
|
||||
| Style | Ghost buttons — transparent bg, var(--text) icon, hover → var(--accent-bg) |
|
||||
|
||||
#### Theme Toggle (Header)
|
||||
|
||||
**OBJECT ID:** `shell-header-theme-toggle`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Component | Icon button with dropdown |
|
||||
| Icon | Sun (light), Moon (dark), Terminal (matrix), Palette (custom) |
|
||||
| Click | Opens theme picker dropdown |
|
||||
| Tooltip | "Switch theme (Ctrl+Shift+T)" |
|
||||
|
||||
#### ↕ `shell-header-v-space-zero` — Header sits flush against tab bar below
|
||||
|
||||
---
|
||||
|
||||
### Section: Sidebar
|
||||
|
||||
**OBJECT ID:** `shell-sidebar`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Purpose | File navigation, document organization, templates |
|
||||
| Width | 240px default, resizable (180–400px), collapsible to 0 |
|
||||
| Background | var(--bg-secondary) |
|
||||
| Border | right 1px solid var(--border) |
|
||||
| Toggle | Cmd/Ctrl+B to show/hide |
|
||||
| Resize | Drag right edge, cursor col-resize |
|
||||
| Persistence | Width and visibility stored in localStorage |
|
||||
|
||||
#### Sidebar Sections
|
||||
|
||||
**OBJECT ID:** `shell-sidebar-sections`
|
||||
|
||||
| Section | Icon | Content |
|
||||
|---------|------|---------|
|
||||
| **Recent** | 🕐 | Last 5 opened documents, sorted by time |
|
||||
| **Favorites** | ⭐ | User-pinned documents |
|
||||
| **Files** | 📁 | Full folder tree with nested structure |
|
||||
| **Templates** | 📋 | Pre-built starting points (Budget, Invoice, Unit Converter, Trip Planner, Blank) |
|
||||
|
||||
Each section is collapsible with a chevron toggle.
|
||||
|
||||
#### File Tree Item
|
||||
|
||||
**OBJECT ID:** `shell-sidebar-file-item`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Height | 28px |
|
||||
| Padding left | 12px + (depth × 16px) for nesting |
|
||||
| Icon | 📄 file / 📁 folder (closed) / 📂 folder (open) |
|
||||
| Label | File/folder name, text-xs, ellipsis on overflow |
|
||||
| Hover | var(--accent-bg) background |
|
||||
| Active | var(--accent-bg) + left 2px accent border |
|
||||
| Right-click | Context menu: Rename, Delete, Duplicate, Move to folder, Add to favorites |
|
||||
| Double-click | Opens document in new tab |
|
||||
| Drag | Reorder within folder, drag between folders |
|
||||
|
||||
#### Sidebar Footer
|
||||
|
||||
**OBJECT ID:** `shell-sidebar-footer`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Content | [+ New Document] button, [+ New Folder] button |
|
||||
| Layout | Horizontal, full width, space-xs gap |
|
||||
| Button style | Ghost, text-2xs, var(--text), hover → var(--accent) |
|
||||
| Position | Sticky bottom of sidebar |
|
||||
| Padding | space-sm |
|
||||
| Border | top 1px solid var(--border) |
|
||||
|
||||
---
|
||||
|
||||
### Section: Tab Bar
|
||||
|
||||
**OBJECT ID:** `shell-tabbar`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Purpose | Multi-document navigation, quick tab management |
|
||||
| Height | 36px |
|
||||
| Background | var(--bg-secondary) |
|
||||
| Border | bottom 1px solid var(--border) |
|
||||
| Layout | Horizontal scroll when tabs overflow |
|
||||
| Position | Between header and editor area |
|
||||
|
||||
#### Tab Item
|
||||
|
||||
**OBJECT ID:** `shell-tabbar-tab`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Min width | 100px |
|
||||
| Max width | 200px |
|
||||
| Padding | 0 space-sm (0 8px) |
|
||||
| Height | 36px (fills bar) |
|
||||
| Label | Document title, text-xs, ellipsis on overflow |
|
||||
| Modified indicator | Dot (6px) before title when unsaved changes |
|
||||
| Close button | × icon, 14px, visible on hover or active tab |
|
||||
| Active state | var(--bg) background, no bottom border (connected to editor) |
|
||||
| Inactive state | var(--bg-secondary), bottom border 1px solid var(--border) |
|
||||
| Hover (inactive) | var(--bg-secondary) lightened slightly |
|
||||
| Drag | Reorder tabs via drag-and-drop |
|
||||
| Middle-click | Close tab |
|
||||
| Double-click | Rename document inline |
|
||||
|
||||
#### New Tab Button
|
||||
|
||||
**OBJECT ID:** `shell-tabbar-new`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Icon | + (16px) |
|
||||
| Size | 36px × 36px (square, fills bar height) |
|
||||
| Style | Ghost, var(--text), hover → var(--accent) |
|
||||
| Click | Creates "Untitled" document and switches to it |
|
||||
| Position | After last tab, before overflow |
|
||||
|
||||
#### Tab Overflow
|
||||
|
||||
**OBJECT ID:** `shell-tabbar-overflow`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Trigger | When tabs exceed container width |
|
||||
| Behavior | Horizontal scroll with mouse wheel or trackpad |
|
||||
| Indicators | Fade gradient on edges when scrollable |
|
||||
| Keyboard | Ctrl+Tab / Ctrl+Shift+Tab to cycle tabs |
|
||||
|
||||
---
|
||||
|
||||
### Section: Main Area
|
||||
|
||||
**OBJECT ID:** `shell-main`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Purpose | Contains the active document's editor and results panel |
|
||||
| Layout | Horizontal flex: editor (flex: 3) + divider + results (flex: 1) |
|
||||
| Background | var(--bg) |
|
||||
| Content | Delegates to Scenario 02 (Editor + Results Panel) |
|
||||
|
||||
#### Editor Pane
|
||||
|
||||
**OBJECT ID:** `shell-main-editor`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Flex | 3 (default ~75%) |
|
||||
| Min width | 300px |
|
||||
| Content | CodeMirror 6 instance (see Scenario 02) |
|
||||
| Overflow | Vertical scroll (editor handles internally) |
|
||||
|
||||
#### Pane Divider (Editor ↔ Results)
|
||||
|
||||
**OBJECT ID:** `shell-main-divider`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Width | 1px visible line |
|
||||
| Hit area | 8px (invisible padding for easy grabbing) |
|
||||
| Cursor | col-resize |
|
||||
| Color | var(--border), hover → var(--accent) |
|
||||
| Transition | background 0.15s |
|
||||
| Double-click | Reset to default 75/25 split |
|
||||
| Persistence | Position stored in localStorage |
|
||||
|
||||
#### Results Pane
|
||||
|
||||
**OBJECT ID:** `shell-main-results`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Flex | 1 (default ~25%) |
|
||||
| Min width | 120px |
|
||||
| Content | Results panel (see Scenario 02) |
|
||||
| Scroll sync | Mirrors editor scroll position |
|
||||
|
||||
---
|
||||
|
||||
### Section: Status Bar
|
||||
|
||||
**OBJECT ID:** `shell-statusbar`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Purpose | Contextual info: cursor position, engine status, theme indicator |
|
||||
| Height | 24px |
|
||||
| Background | var(--bg-secondary) |
|
||||
| Border | top 1px solid var(--border) |
|
||||
| Font | text-3xs, monospace |
|
||||
| Color | var(--text) |
|
||||
| Layout | Horizontal: left-info + right-info |
|
||||
|
||||
Full specification in [1.2 — Status Bar](../1.2-status-bar/1.2-status-bar.md).
|
||||
|
||||
---
|
||||
|
||||
## Page States
|
||||
|
||||
| State | When | Appearance | Actions |
|
||||
|-------|------|------------|---------|
|
||||
| **First Launch** | No localStorage data | Sidebar open with Templates section expanded. Single tab "Welcome" with demo calctext. Theme follows OS preference. | User can start typing, explore templates, or create new doc |
|
||||
| **Returning User** | localStorage has documents | Restores: last open tabs, active tab, sidebar state, theme, divider positions | Resume exactly where they left off |
|
||||
| **Empty Workspace** | All documents deleted | Editor shows subtle placeholder: "Create a new document or choose a template to get started" | + New Document button prominent, Templates section highlighted |
|
||||
| **Engine Loading** | WASM initializing | Status bar shows "Engine loading..." with pulse animation. Editor is editable. Results show "—" | Editor works, results appear once engine is ready |
|
||||
| **Offline** | No network | Subtle indicator in status bar. All features work (localStorage). Currency rates may be stale. | Full functionality, offline banner only if currency conversion attempted |
|
||||
|
||||
---
|
||||
|
||||
## Interactions & Keyboard Shortcuts
|
||||
|
||||
| Shortcut | Action |
|
||||
|----------|--------|
|
||||
| Cmd/Ctrl + B | Toggle sidebar |
|
||||
| Cmd/Ctrl + N | New document |
|
||||
| Cmd/Ctrl + W | Close active tab |
|
||||
| Cmd/Ctrl + Tab | Next tab |
|
||||
| Cmd/Ctrl + Shift + Tab | Previous tab |
|
||||
| Cmd/Ctrl + 1–9 | Switch to tab N |
|
||||
| Cmd/Ctrl + Shift + T | Open theme picker |
|
||||
| Cmd/Ctrl + , | Open settings |
|
||||
| Cmd/Ctrl + S | Force save (visual confirmation — auto-save is default) |
|
||||
|
||||
---
|
||||
|
||||
## Responsive Behavior
|
||||
|
||||
| Breakpoint | Layout Changes |
|
||||
|------------|----------------|
|
||||
| **>= 1024px** | Full 3-panel: sidebar + editor + results |
|
||||
| **768–1023px** | Sidebar becomes overlay drawer (hamburger toggle). Editor + results remain side-by-side |
|
||||
| **< 768px** | Single column. Sidebar = full-screen drawer. Results = bottom tray (collapsible). Tabs = horizontal scroll. Header = 44px (touch). |
|
||||
|
||||
### Mobile Results Tray
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Collapsed height | 48px (shows last result + drag handle) |
|
||||
| Expanded height | 40vh |
|
||||
| Drag handle | 32px × 4px pill, centered, var(--border) |
|
||||
| Swipe up | Expand tray |
|
||||
| Swipe down | Collapse tray |
|
||||
| Tap collapsed | Expand tray |
|
||||
|
||||
### Mobile Sidebar Drawer
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Width | 85vw (max 320px) |
|
||||
| Background | var(--bg) |
|
||||
| Overlay | 50% black backdrop |
|
||||
| Animation | Slide from left, 200ms ease-out |
|
||||
| Close | Tap backdrop, swipe left, or X button |
|
||||
|
||||
---
|
||||
|
||||
## Theme Integration
|
||||
|
||||
The app shell's colors are entirely driven by CSS custom properties. Switching themes means swapping the property values on `:root`. No component changes needed.
|
||||
|
||||
| Theme | --bg | --bg-secondary | --text | --accent | Special |
|
||||
|-------|------|----------------|--------|----------|---------|
|
||||
| **Light** | #fff | #f8f9fa | #6b6375 | #6366f1 | — |
|
||||
| **Dark** | #16171d | #1a1b23 | #9ca3af | #818cf8 | — |
|
||||
| **Matrix** | #0a0a0a | #0f0f0f | #00ff41 | #00ff41 | Monospace everywhere. Subtle scanline overlay. Cursor blink green. |
|
||||
| **Custom** | User-defined | User-defined | User-defined | User-defined | Accent color picker + base tone (warm/cool/neutral) |
|
||||
|
||||
Theme selection persisted in localStorage as `calctext-theme`.
|
||||
|
||||
---
|
||||
|
||||
## localStorage Schema
|
||||
|
||||
```typescript
|
||||
interface CalcTextStorage {
|
||||
// Documents
|
||||
documents: Document[]
|
||||
folders: Folder[]
|
||||
activeTabId: string
|
||||
openTabIds: string[]
|
||||
|
||||
// Layout
|
||||
sidebarWidth: number
|
||||
sidebarVisible: boolean
|
||||
dividerPosition: number // percentage
|
||||
|
||||
// Preferences
|
||||
theme: 'light' | 'dark' | 'matrix' | string // string for custom
|
||||
customTheme?: ThemeTokens
|
||||
accentColor?: string
|
||||
|
||||
// State
|
||||
lastOpenedAt: string // ISO timestamp
|
||||
}
|
||||
|
||||
interface Document {
|
||||
id: string
|
||||
title: string
|
||||
content: string
|
||||
folderId: string | null
|
||||
isFavorite: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
interface Folder {
|
||||
id: string
|
||||
name: string
|
||||
parentId: string | null
|
||||
order: number
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- **Cross-platform portability:** All layout patterns map to native equivalents. Sidebar → NavigationView (SwiftUI) / side_panel (iced). Tab bar → TabView. Status bar → standard OS status bar pattern.
|
||||
- **Performance:** Only the active tab's CodeMirror instance should be in DOM. Inactive tabs store content in memory, restore on switch.
|
||||
- **Auto-save:** Documents save to localStorage on every change (debounced 500ms). No explicit "save" needed, but Cmd+S provides visual confirmation.
|
||||
- **Sidebar resize:** Use ResizeObserver + mouse events. Store width in localStorage. Minimum 180px, maximum 400px.
|
||||
- **Tab management:** Maximum suggested tabs: 20. Beyond that, show "too many tabs" hint. No hard limit.
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
| # | Question | Context | Status |
|
||||
|---|----------|---------|--------|
|
||||
| 1 | Should Matrix theme have a subtle CRT scanline effect? | Could be fun but might impact readability | 🔴 Open |
|
||||
| 2 | Should we support tab groups / workspaces? | Multiple sets of tabs for different projects | 🔴 Open — defer to v2 |
|
||||
| 3 | Max localStorage size for documents? | ~5MB browser limit. Need strategy for large collections. | 🟡 In Discussion — may need IndexedDB |
|
||||
|
||||
---
|
||||
|
||||
## Checklist
|
||||
|
||||
- [x] Page purpose clear
|
||||
- [x] All Object IDs assigned
|
||||
- [x] Layout structure defined (desktop, tablet, mobile)
|
||||
- [x] Spacing tokens documented
|
||||
- [x] Typography scale applied
|
||||
- [x] States documented (first launch, returning, empty, loading, offline)
|
||||
- [x] Keyboard shortcuts defined
|
||||
- [x] Responsive breakpoints specified
|
||||
- [x] Theme integration documented
|
||||
- [x] localStorage schema defined
|
||||
- [x] Cross-platform portability noted
|
||||
- [x] Open questions captured
|
||||
|
||||
---
|
||||
|
||||
**Next Step:** → [Status Bar](../1.2-status-bar/1.2-status-bar.md)
|
||||
|
||||
---
|
||||
|
||||
_Created using Whiteport Design Studio (WDS) methodology_
|
||||
@@ -0,0 +1,124 @@
|
||||
# 1.2 — Status Bar
|
||||
|
||||
**Previous Step:** ← [App Shell](../1.1-app-shell/1.1-app-shell.md)
|
||||
|
||||
---
|
||||
|
||||
## Page Metadata
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Scenario** | 01 — Workspace Shell |
|
||||
| **Page Number** | 1.2 |
|
||||
| **Platform** | Web (PWA), portable to macOS/Windows |
|
||||
| **Page Type** | Persistent UI Strip (embedded in App Shell) |
|
||||
| **Viewport** | All breakpoints |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Page Purpose:** Provide at-a-glance contextual information about the current document, engine status, and workspace state. Acts as a persistent information bar at the bottom of the workspace.
|
||||
|
||||
**User Situation:** User is working in the editor and glances down for context — current line/column, whether the engine is ready, what theme is active, and document stats.
|
||||
|
||||
**Success Criteria:**
|
||||
- Information readable at a glance without interrupting flow
|
||||
- No interactive elements that accidentally trigger (info-only, with 2 clickable shortcuts)
|
||||
- Consistent across all themes
|
||||
|
||||
---
|
||||
|
||||
## Layout Structure
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ [Ln 12, Col 8] [42 lines] │ [Engine ● Ready] [Dark 🎨] │
|
||||
│ ← left-aligned info │ right-aligned info → │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Height: 24px. Full width. Background: var(--bg-secondary). Border-top: 1px solid var(--border).
|
||||
|
||||
---
|
||||
|
||||
## Spacing
|
||||
|
||||
| Property | Token | Pixels |
|
||||
|----------|-------|--------|
|
||||
| Height | — | 24px |
|
||||
| Padding horizontal | space-md | 12px |
|
||||
| Item gap | space-md | 12px |
|
||||
| Dot size (engine status) | — | 6px |
|
||||
| Dot margin-right | space-2xs | 4px |
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
| Element | Size | Weight | Typeface | Color |
|
||||
|---------|------|--------|----------|-------|
|
||||
| All status text | text-3xs | 400 | system mono | var(--text) |
|
||||
| Engine status (ready) | text-3xs | 400 | system mono | var(--success) |
|
||||
| Engine status (loading) | text-3xs | 400 | system mono | var(--warning) |
|
||||
| Theme name | text-3xs | 500 | system mono | var(--text) |
|
||||
|
||||
---
|
||||
|
||||
## Status Items
|
||||
|
||||
### Left Group
|
||||
|
||||
**OBJECT ID:** `statusbar-left`
|
||||
|
||||
| Item | Content | Update Trigger |
|
||||
|------|---------|----------------|
|
||||
| Cursor position | `Ln {line}, Col {col}` | Cursor movement |
|
||||
| Line count | `{n} lines` | Document change |
|
||||
| Selection (conditional) | `{n} selected` | When text selected |
|
||||
|
||||
### Right Group
|
||||
|
||||
**OBJECT ID:** `statusbar-right`
|
||||
|
||||
| Item | Content | Behavior |
|
||||
|------|---------|----------|
|
||||
| Engine status | `● Ready` (green dot) or `◌ Loading...` (amber, pulse) | Auto-updates on engine state change |
|
||||
| Theme indicator | `{Theme Name} 🎨` | Click → opens theme picker (same as header button) |
|
||||
| Offline (conditional) | `📡 Offline` | Only visible when offline |
|
||||
|
||||
---
|
||||
|
||||
## States
|
||||
|
||||
| State | Engine Indicator | Additional |
|
||||
|-------|-----------------|------------|
|
||||
| Ready | ● green dot, "Ready" | — |
|
||||
| Loading | ◌ amber dot, pulse animation, "Loading..." | — |
|
||||
| Error | ● red dot, "Engine error" | Tooltip with error message |
|
||||
| Offline | ● green dot, "Ready" | + "📡 Offline" appended |
|
||||
|
||||
---
|
||||
|
||||
## Responsive
|
||||
|
||||
| Breakpoint | Behavior |
|
||||
|------------|----------|
|
||||
| >= 768px | Full status bar, all items visible |
|
||||
| < 768px | Simplified: cursor position + engine dot only. Theme and line count hidden. |
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Status bar reads from editor state (cursor, selection, line count) and engine hook (ready, error)
|
||||
- Theme indicator is the only clickable element — keeps status bar non-disruptive
|
||||
- On macOS/Windows: maps to native status bar or window title bar info
|
||||
|
||||
---
|
||||
|
||||
**Previous Step:** ← [App Shell](../1.1-app-shell/1.1-app-shell.md)
|
||||
|
||||
---
|
||||
|
||||
_Created using Whiteport Design Studio (WDS) methodology_
|
||||
@@ -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_
|
||||
@@ -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+1–9** | 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_
|
||||
@@ -0,0 +1,328 @@
|
||||
# 4.1 — Sidebar & File Organization
|
||||
|
||||
**Previous Step:** ← [Tab Bar](../../03-document-management/3.1-tab-bar/3.1-tab-bar.md)
|
||||
**Next Step:** → [Templates](../4.2-templates/4.2-templates.md)
|
||||
|
||||
---
|
||||
|
||||
## Page Metadata
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Scenario** | 04 — File Organization |
|
||||
| **Page Number** | 4.1 |
|
||||
| **Platform** | Web (PWA), portable to macOS/Windows |
|
||||
| **Page Type** | Collapsible Side Panel (within App Shell) |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Page Purpose:** Organize and navigate calctext documents. The sidebar provides a persistent file tree with folders, recent files, favorites, and templates — transforming CalcText from a single-use calculator into a workspace where calculations are organized and retrievable.
|
||||
|
||||
**Success Criteria:**
|
||||
- Users find any document in < 3 seconds
|
||||
- Folder hierarchy is intuitive (create, nest, rename, delete)
|
||||
- Recent and Favorites provide quick access without browsing
|
||||
- Sidebar never feels cluttered even with 50+ documents
|
||||
|
||||
---
|
||||
|
||||
## Layout Structure
|
||||
|
||||
```
|
||||
┌──────────────────────┐
|
||||
│ 🔍 Search... │ 32px — search bar
|
||||
├──────────────────────┤
|
||||
│ │
|
||||
│ ▸ Recent │ section header (collapsible)
|
||||
│ 📄 Budget │
|
||||
│ 📄 Quick Math │
|
||||
│ 📄 Invoice #42 │
|
||||
│ │
|
||||
│ ▸ Favorites │ section header
|
||||
│ ⭐ Monthly Budget │
|
||||
│ ⭐ Tax Calculator │
|
||||
│ │
|
||||
│ ▾ Files │ section header (expanded)
|
||||
│ 📁 Work │ folder
|
||||
│ │ 📄 Budget │ file in folder
|
||||
│ │ 📄 Invoice │
|
||||
│ 📁 Personal │ folder
|
||||
│ │ 📁 Travel │ nested folder
|
||||
│ │ │ 📄 Trip Cost │
|
||||
│ 📄 Scratch │ root-level file
|
||||
│ │
|
||||
│ ▸ Templates │ section header
|
||||
│ │
|
||||
├──────────────────────┤
|
||||
│ [+ Doc] [+ Folder] │ sticky footer
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spacing
|
||||
|
||||
| Property | Token | Pixels |
|
||||
|----------|-------|--------|
|
||||
| Sidebar padding top | space-xs | 6px |
|
||||
| Search bar height | — | 32px |
|
||||
| Search bar margin | space-xs | 6px all sides |
|
||||
| Section header height | — | 28px |
|
||||
| Section header padding left | space-sm | 8px |
|
||||
| File item height | — | 28px |
|
||||
| File item padding left (root) | space-md | 12px |
|
||||
| File item indent per depth | — | 16px |
|
||||
| File icon size | — | 16px |
|
||||
| File icon-to-label gap | space-xs | 6px |
|
||||
| Section gap | space-xs | 6px |
|
||||
| Footer height | — | 40px |
|
||||
| Footer padding | space-xs | 6px |
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
| Element | Size | Weight | Color |
|
||||
|---------|------|--------|-------|
|
||||
| Search placeholder | text-xs | 400 | var(--text) at 50% |
|
||||
| Section header | text-2xs | 600 | var(--text) at 70% |
|
||||
| File name | text-xs | 400 | var(--text) |
|
||||
| File name (active) | text-xs | 500 | var(--text-h) |
|
||||
| Folder name | text-xs | 500 | var(--text) |
|
||||
| Footer buttons | text-2xs | 400 | var(--text), hover → var(--accent) |
|
||||
| File count badge | text-3xs | 400 | var(--text) at 40% |
|
||||
|
||||
---
|
||||
|
||||
## Search Bar
|
||||
|
||||
**OBJECT ID:** `sidebar-search`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Placeholder | "Search documents..." |
|
||||
| Background | var(--bg) |
|
||||
| Border | 1px solid var(--border), focus → var(--accent-border) |
|
||||
| Border radius | 4px |
|
||||
| Icon | 🔍 magnifier, 14px, var(--text) at 40% |
|
||||
| Padding | 4px 8px 4px 28px (icon offset) |
|
||||
| Behavior | Filters file tree in real-time as user types |
|
||||
| Clear | × button appears when text entered |
|
||||
| Keyboard | Ctrl/Cmd+P opens/focuses search (like VS Code quick open) |
|
||||
| Results | Flat list of matching files, ranked by recency. Highlights matching text. |
|
||||
| Empty state | "No documents match '{query}'" |
|
||||
|
||||
---
|
||||
|
||||
## Section: Recent
|
||||
|
||||
**OBJECT ID:** `sidebar-recent`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Content | Last 5 opened documents, sorted by lastOpened timestamp |
|
||||
| Collapsible | Yes, chevron toggle |
|
||||
| Default state | Expanded on first launch, remembers toggle |
|
||||
| Item display | File icon + name only (no folder path) |
|
||||
| Empty state | "No recent documents" in text-3xs, muted |
|
||||
| Update trigger | Opening any document pushes it to top, bumps oldest |
|
||||
|
||||
---
|
||||
|
||||
## Section: Favorites
|
||||
|
||||
**OBJECT ID:** `sidebar-favorites`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Content | User-pinned documents, ordered manually (drag) |
|
||||
| Collapsible | Yes |
|
||||
| Default state | Collapsed if empty, expanded if has items |
|
||||
| Item display | ⭐ icon + name |
|
||||
| Add to favorites | Right-click file → "Add to Favorites", or drag file into section |
|
||||
| Remove | Right-click → "Remove from Favorites" |
|
||||
| Empty state | "Drag files here or right-click → Add to Favorites" |
|
||||
|
||||
---
|
||||
|
||||
## Section: Files (Tree)
|
||||
|
||||
**OBJECT ID:** `sidebar-files`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Content | Complete folder tree with all documents |
|
||||
| Collapsible | Yes (section level) |
|
||||
| Default state | Expanded |
|
||||
| Sort | Folders first, then files. Alphabetical within each group |
|
||||
| Max depth | 3 levels (root → folder → subfolder → files). Prevents over-nesting |
|
||||
|
||||
### Folder Item
|
||||
|
||||
**OBJECT ID:** `sidebar-folder`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Icon | 📁 (closed) / 📂 (open) — or chevron ▸/▾ |
|
||||
| Click | Toggle expand/collapse |
|
||||
| Double-click | Rename inline |
|
||||
| Right-click | Context menu |
|
||||
| Drag | Reorder within parent. Drop files into folder. |
|
||||
| Drop target | Highlight with var(--accent-bg) + 2px dashed var(--accent) border |
|
||||
| Badge | File count in parentheses: `Work (3)` — text-3xs, muted |
|
||||
|
||||
### File Item
|
||||
|
||||
**OBJECT ID:** `sidebar-file`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Icon | 📄 (default) — could be themed per type later |
|
||||
| Click | Open in tab (or switch to existing tab if already open) |
|
||||
| Double-click | Open + rename inline |
|
||||
| Hover | var(--accent-bg) background |
|
||||
| Active | var(--accent-bg) + left 2px solid var(--accent) border |
|
||||
| Active = | Currently open in active tab |
|
||||
| Open indicator | Subtle dot or underline if open in any tab (even if not active) |
|
||||
| Drag | Move between folders. Drag to tab bar to open. |
|
||||
| Right-click | Context menu |
|
||||
|
||||
### File Context Menu
|
||||
|
||||
| Item | Action |
|
||||
|------|--------|
|
||||
| Open | Open in new tab |
|
||||
| Open in New Tab | Open without closing current |
|
||||
| — | separator |
|
||||
| Rename | Inline rename |
|
||||
| Duplicate | Copy with "(copy)" suffix |
|
||||
| Add to Favorites | Toggle ⭐ |
|
||||
| — | separator |
|
||||
| Move to... | Submenu with folder list |
|
||||
| — | separator |
|
||||
| Delete | Confirm dialog → 5-second undo toast |
|
||||
|
||||
### Folder Context Menu
|
||||
|
||||
| Item | Action |
|
||||
|------|--------|
|
||||
| New Document Here | Create file inside this folder |
|
||||
| New Subfolder | Create nested folder (max depth 3) |
|
||||
| — | separator |
|
||||
| Rename | Inline rename |
|
||||
| — | separator |
|
||||
| Delete Folder | Must be empty. If not: "Move contents to root first." |
|
||||
|
||||
---
|
||||
|
||||
## Section: Templates
|
||||
|
||||
**OBJECT ID:** `sidebar-templates`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Collapsible | Yes |
|
||||
| Default state | Expanded on first launch |
|
||||
| Content | Pre-built starting documents |
|
||||
|
||||
Full specification in [4.2 — Templates](../4.2-templates/4.2-templates.md).
|
||||
|
||||
---
|
||||
|
||||
## Sidebar Footer
|
||||
|
||||
**OBJECT ID:** `sidebar-footer`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Position | Sticky bottom |
|
||||
| Background | var(--bg-secondary) |
|
||||
| Border top | 1px solid var(--border) |
|
||||
| Layout | Two buttons side-by-side |
|
||||
| Buttons | `[+ Document]` `[+ Folder]` — ghost style |
|
||||
| New Document | Creates at root level, opens in tab |
|
||||
| New Folder | Creates at root level, inline rename active |
|
||||
|
||||
---
|
||||
|
||||
## Drag and Drop
|
||||
|
||||
| Drag Source | Drop Target | Behavior |
|
||||
|-------------|-------------|----------|
|
||||
| File | Folder | Move file into folder |
|
||||
| File | Between files | Reorder within same folder |
|
||||
| File | Tab bar | Open file in new tab |
|
||||
| File | Favorites section | Add to favorites |
|
||||
| Folder | Between folders | Reorder at same depth |
|
||||
| Tab | Sidebar folder | Move document to folder |
|
||||
|
||||
### Drop Visual Feedback
|
||||
|
||||
| State | Appearance |
|
||||
|-------|------------|
|
||||
| Valid target hover | var(--accent-bg) background, 2px dashed var(--accent) border |
|
||||
| Invalid target | No visual change (drop not accepted) |
|
||||
| Insertion line | 2px solid var(--accent) horizontal line at insertion point |
|
||||
| Dragging item | 60% opacity, subtle shadow |
|
||||
|
||||
---
|
||||
|
||||
## Resize Handle
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Position | Right edge of sidebar |
|
||||
| Width | 1px visible, 8px hit area |
|
||||
| Cursor | col-resize |
|
||||
| Color | var(--border), hover/drag → var(--accent) |
|
||||
| Constraints | Min 180px, max 400px |
|
||||
| Double-click | Reset to default 240px |
|
||||
| Persistence | Width stored in localStorage |
|
||||
|
||||
---
|
||||
|
||||
## Responsive Behavior
|
||||
|
||||
| Breakpoint | Behavior |
|
||||
|------------|----------|
|
||||
| >= 1024px | Persistent side panel, resizable |
|
||||
| 768–1023px | Overlay drawer, hamburger toggle in header |
|
||||
| < 768px | Full-screen drawer (85vw, max 320px) |
|
||||
|
||||
### Mobile Drawer
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Trigger | Hamburger menu (≡) in header |
|
||||
| Width | 85vw, max 320px |
|
||||
| Overlay | 50% black backdrop |
|
||||
| Animation | Slide from left, 200ms ease-out |
|
||||
| Close | Tap backdrop, swipe left, or × button top-right |
|
||||
| File tap | Opens document, auto-closes drawer |
|
||||
|
||||
---
|
||||
|
||||
## Page States
|
||||
|
||||
| State | When | Behavior |
|
||||
|-------|------|----------|
|
||||
| **Empty** | No documents or folders | Show: "Welcome! Create your first document or pick a template." + prominent buttons |
|
||||
| **Few files** (<5) | Early usage | All sections visible, Templates expanded to encourage exploration |
|
||||
| **Many files** (>20) | Power user | Search becomes critical. Sections collapsed by default except Files |
|
||||
| **Search active** | User typed in search | Tree replaced by flat filtered list. Sections hidden. |
|
||||
| **Dragging** | File/folder being moved | Drop targets highlighted. Invalid areas dimmed. |
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- **Virtual scrolling:** Not needed until 500+ items. Standard DOM rendering is fine for typical usage.
|
||||
- **Folder persistence:** `folders: Folder[]` in localStorage with `parentId` for tree structure.
|
||||
- **Sort stability:** Alphabetical sort is stable — user manual ordering within a folder stored as `order` field.
|
||||
- **Cross-platform:** Maps to NSOutlineView (macOS), Tree widget (iced/Windows). Same data model.
|
||||
|
||||
---
|
||||
|
||||
_Created using Whiteport Design Studio (WDS) methodology_
|
||||
@@ -0,0 +1,219 @@
|
||||
# 4.2 — Templates
|
||||
|
||||
**Previous Step:** ← [Sidebar](../4.1-sidebar/4.1-sidebar.md)
|
||||
**Next Step:** → [Theme System](../../05-theming/5.1-theme-system/5.1-theme-system.md)
|
||||
|
||||
---
|
||||
|
||||
## Page Metadata
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Scenario** | 04 — File Organization |
|
||||
| **Page Number** | 4.2 |
|
||||
| **Platform** | Web (PWA), portable to macOS/Windows |
|
||||
| **Page Type** | Sidebar Section + Modal (template preview) |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Page Purpose:** Provide ready-made starting documents that showcase CalcText's capabilities and help users get productive immediately. Templates are the product's best onboarding tool — they show by example.
|
||||
|
||||
**Success Criteria:**
|
||||
- First-time user finds a relevant template within 10 seconds
|
||||
- Templates demonstrate the product's unique features (variables, units, currencies, aggregators)
|
||||
- Using a template creates a new document (never modifies the template)
|
||||
|
||||
---
|
||||
|
||||
## Template Library
|
||||
|
||||
| Template | Description | Showcases |
|
||||
|----------|-------------|-----------|
|
||||
| **Budget** | Monthly income/expenses with categories | Variables, aggregators (sum, total), percentages |
|
||||
| **Invoice** | Service invoice with line items and tax | Variables, multiplication, percentages, currency |
|
||||
| **Unit Converter** | Common conversions with examples | Unit expressions (kg to lb, km to mi, °C to °F) |
|
||||
| **Trip Planner** | Travel budget with currency conversion | Currency conversion, date math, variables |
|
||||
| **Loan Calculator** | Mortgage/loan with monthly payments | Financial functions, percentages, variables |
|
||||
| **Blank** | Empty document | — (clean start) |
|
||||
|
||||
---
|
||||
|
||||
## Sidebar Templates Section
|
||||
|
||||
**OBJECT ID:** `sidebar-templates`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Section header | "Templates" with 📋 icon |
|
||||
| Collapsible | Yes |
|
||||
| Default | Expanded on first launch, collapsed after first document created |
|
||||
| Item height | 32px (slightly taller than files — more padding) |
|
||||
| Item icon | Colored dot per template (visual distinction) |
|
||||
| Item label | Template name, text-xs |
|
||||
| Item sublabel | Brief description, text-3xs, muted, truncated |
|
||||
|
||||
### Template Item Interaction
|
||||
|
||||
| Action | Behavior |
|
||||
|--------|----------|
|
||||
| Click | Create new document from template. Title = template name. Opens in new tab. |
|
||||
| Hover | Show full description in tooltip |
|
||||
| Right-click | "Preview" option |
|
||||
|
||||
---
|
||||
|
||||
## Template Colors (Icon Dots)
|
||||
|
||||
| Template | Dot Color |
|
||||
|----------|-----------|
|
||||
| Budget | #10b981 (emerald) |
|
||||
| Invoice | #6366f1 (indigo) |
|
||||
| Unit Converter | #0d9488 (teal) |
|
||||
| Trip Planner | #f59e0b (amber) |
|
||||
| Loan Calculator | #7c3aed (violet) |
|
||||
| Blank | var(--border) (gray) |
|
||||
|
||||
---
|
||||
|
||||
## Template Content
|
||||
|
||||
### Budget Template
|
||||
```
|
||||
# Monthly Budget
|
||||
|
||||
// Income
|
||||
salary = 5000
|
||||
freelance = 1200
|
||||
total_income = salary + freelance
|
||||
|
||||
// Housing
|
||||
rent = 1500
|
||||
utilities = 150
|
||||
insurance = 80
|
||||
|
||||
// Living
|
||||
groceries = 400
|
||||
transport = 120
|
||||
subscriptions = 45
|
||||
|
||||
// Summary
|
||||
total_expenses = sum
|
||||
savings = total_income - total_expenses
|
||||
savings_rate = savings / total_income
|
||||
```
|
||||
|
||||
### Invoice Template
|
||||
```
|
||||
# Invoice #001
|
||||
|
||||
// Client: [Client Name]
|
||||
// Date: [Date]
|
||||
|
||||
// Services
|
||||
web_design = 2500
|
||||
development = 4000
|
||||
consulting = 150 * 8
|
||||
|
||||
// Expenses
|
||||
hosting = 29.99
|
||||
domain = 12.00
|
||||
|
||||
subtotal = sum
|
||||
|
||||
// Tax
|
||||
tax_rate = 10%
|
||||
tax = subtotal * tax_rate
|
||||
total = subtotal + tax
|
||||
```
|
||||
|
||||
### Unit Converter Template
|
||||
```
|
||||
# Unit Converter
|
||||
|
||||
// Weight
|
||||
75 kg in lb
|
||||
2.5 lb in kg
|
||||
100 g in oz
|
||||
|
||||
// Distance
|
||||
10 km in mi
|
||||
26.2 mi in km
|
||||
5280 ft in m
|
||||
|
||||
// Temperature
|
||||
100 °C in °F
|
||||
72 °F in °C
|
||||
0 °C in K
|
||||
|
||||
// Data
|
||||
1 GB in MB
|
||||
500 MB in GB
|
||||
1 TB in GB
|
||||
```
|
||||
|
||||
### Trip Planner Template
|
||||
```
|
||||
# Trip Planner
|
||||
|
||||
// Budget
|
||||
budget = $3000
|
||||
|
||||
// Flights
|
||||
flight_out = $450
|
||||
flight_back = $380
|
||||
|
||||
// Hotel
|
||||
nights = 7
|
||||
rate_per_night = $120
|
||||
hotel_total = nights * rate_per_night
|
||||
|
||||
// Daily expenses
|
||||
daily_food = $50
|
||||
daily_transport = $20
|
||||
daily_activities = $35
|
||||
daily_total = daily_food + daily_transport + daily_activities
|
||||
trip_expenses = daily_total * nights
|
||||
|
||||
// Summary
|
||||
total_cost = flight_out + flight_back + hotel_total + trip_expenses
|
||||
remaining = budget - total_cost
|
||||
```
|
||||
|
||||
### Loan Calculator Template
|
||||
```
|
||||
# Loan Calculator
|
||||
|
||||
// Loan Details
|
||||
principal = 250000
|
||||
annual_rate = 6.5%
|
||||
years = 30
|
||||
|
||||
// Monthly Calculation
|
||||
monthly_rate = annual_rate / 12
|
||||
num_payments = years * 12
|
||||
|
||||
// Monthly Payment
|
||||
monthly_payment = principal * (monthly_rate * (1 + monthly_rate) ^ num_payments) / ((1 + monthly_rate) ^ num_payments - 1)
|
||||
|
||||
// Total Cost
|
||||
total_paid = monthly_payment * num_payments
|
||||
total_interest = total_paid - principal
|
||||
|
||||
// Summary
|
||||
interest_ratio = total_interest / principal
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- Templates are hardcoded in the app (not fetched from server). Stored as string constants.
|
||||
- Creating from template: deep copy content, assign new id/title, save to documents array.
|
||||
- Future: user-created templates (save document as template). Defer to v2.
|
||||
- Cross-platform: same template content across all platforms.
|
||||
|
||||
---
|
||||
|
||||
_Created using Whiteport Design Studio (WDS) methodology_
|
||||
@@ -0,0 +1,371 @@
|
||||
# 5.1 — Theme System
|
||||
|
||||
**Previous Step:** ← [Templates](../../04-file-organization/4.2-templates/4.2-templates.md)
|
||||
**Next Step:** → [Mobile Experience](../../06-mobile-experience/6.1-mobile-shell/6.1-mobile-shell.md)
|
||||
|
||||
---
|
||||
|
||||
## Page Metadata
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Scenario** | 05 — Theming |
|
||||
| **Page Number** | 5.1 |
|
||||
| **Platform** | Web (PWA), portable to macOS/Windows |
|
||||
| **Page Type** | Dropdown Panel + Settings Section |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Page Purpose:** Enable users to personalize their workspace with preset themes and custom accent colors. Theming is a personality differentiator — especially the Matrix theme, which gives CalcText a unique identity in the calculator app market.
|
||||
|
||||
**Success Criteria:**
|
||||
- Theme switches instantly (< 16ms, no flash of unstyled content)
|
||||
- Matrix theme makes users want to screenshot and share
|
||||
- Custom accent color gives ownership feeling
|
||||
- Theme persists across sessions
|
||||
|
||||
---
|
||||
|
||||
## Theme Architecture
|
||||
|
||||
All theming works through CSS custom properties on `:root`. Switching themes = swapping property values. No component changes, no re-renders beyond what CSS handles natively.
|
||||
|
||||
```typescript
|
||||
interface ThemeDefinition {
|
||||
id: string
|
||||
name: string
|
||||
icon: string // emoji or svg
|
||||
tokens: {
|
||||
// Backgrounds
|
||||
bg: string
|
||||
bgSecondary: string
|
||||
codeBg: string
|
||||
|
||||
// Text
|
||||
text: string
|
||||
textH: string
|
||||
|
||||
// Borders
|
||||
border: string
|
||||
|
||||
// Accent
|
||||
accent: string
|
||||
accentBg: string
|
||||
accentBorder: string
|
||||
|
||||
// Semantic
|
||||
success: string
|
||||
warning: string
|
||||
error: string
|
||||
|
||||
// Syntax highlighting
|
||||
syntaxVariable: string
|
||||
syntaxNumber: string
|
||||
syntaxOperator: string
|
||||
syntaxKeyword: string
|
||||
syntaxFunction: string
|
||||
syntaxCurrency: string
|
||||
syntaxComment: string
|
||||
syntaxHeading: string
|
||||
|
||||
// Result colors
|
||||
resultNumber: string
|
||||
resultUnit: string
|
||||
resultCurrency: string
|
||||
resultDatetime: string
|
||||
resultBoolean: string
|
||||
|
||||
// Stripes
|
||||
stripe: string
|
||||
|
||||
// Special
|
||||
fontOverride?: string // for Matrix: force monospace everywhere
|
||||
specialEffect?: string // CSS class for effects like scanlines
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Preset Themes
|
||||
|
||||
### Light
|
||||
|
||||
| Token | Value |
|
||||
|-------|-------|
|
||||
| --bg | #ffffff |
|
||||
| --bg-secondary | #f8f9fa |
|
||||
| --text | #6b6375 |
|
||||
| --text-h | #08060d |
|
||||
| --border | #e5e4e7 |
|
||||
| --accent | #6366f1 |
|
||||
| --stripe | rgba(0, 0, 0, 0.02) |
|
||||
| Feel | Clean, airy, professional |
|
||||
|
||||
### Dark
|
||||
|
||||
| Token | Value |
|
||||
|-------|-------|
|
||||
| --bg | #16171d |
|
||||
| --bg-secondary | #1a1b23 |
|
||||
| --text | #9ca3af |
|
||||
| --text-h | #f3f4f6 |
|
||||
| --border | #2e303a |
|
||||
| --accent | #818cf8 |
|
||||
| --stripe | rgba(255, 255, 255, 0.025) |
|
||||
| Feel | Calm, focused, modern |
|
||||
|
||||
### Matrix
|
||||
|
||||
| Token | Value |
|
||||
|-------|-------|
|
||||
| --bg | #0a0a0a |
|
||||
| --bg-secondary | #0f1a0f |
|
||||
| --text | #00ff41 |
|
||||
| --text-h | #33ff66 |
|
||||
| --border | #003300 |
|
||||
| --accent | #00ff41 |
|
||||
| --stripe | rgba(0, 255, 65, 0.03) |
|
||||
| --syntax-* | Green spectrum (#00cc33 to #39ff14) |
|
||||
| --result-currency | #ffff00 (yellow — stands out in green) |
|
||||
| fontOverride | `'Courier New', 'Fira Code', monospace` |
|
||||
| specialEffect | `matrix-scanlines` |
|
||||
| Feel | Iconic, hackery, fun |
|
||||
|
||||
#### Matrix Special Effects
|
||||
|
||||
**Scanlines (optional, subtle):**
|
||||
```css
|
||||
.matrix-scanlines::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
pointer-events: none;
|
||||
background: repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(0, 0, 0, 0.08) 2px,
|
||||
rgba(0, 0, 0, 0.08) 4px
|
||||
);
|
||||
z-index: 9999;
|
||||
}
|
||||
```
|
||||
|
||||
**Cursor glow:**
|
||||
```css
|
||||
.matrix-theme .cm-cursor {
|
||||
border-color: #00ff41;
|
||||
box-shadow: 0 0 4px #00ff41, 0 0 8px rgba(0, 255, 65, 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
**Text glow (subtle):**
|
||||
```css
|
||||
.matrix-theme .result-value {
|
||||
text-shadow: 0 0 2px rgba(0, 255, 65, 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
### Midnight
|
||||
|
||||
| Token | Value |
|
||||
|-------|-------|
|
||||
| --bg | #0f172a |
|
||||
| --bg-secondary | #1e293b |
|
||||
| --text | #94a3b8 |
|
||||
| --text-h | #e2e8f0 |
|
||||
| --border | #334155 |
|
||||
| --accent | #38bdf8 (sky-400) |
|
||||
| --stripe | rgba(56, 189, 248, 0.03) |
|
||||
| Feel | Deep blue, serene, late-night coding |
|
||||
|
||||
### Warm
|
||||
|
||||
| Token | Value |
|
||||
|-------|-------|
|
||||
| --bg | #fffbf5 |
|
||||
| --bg-secondary | #fef3e2 |
|
||||
| --text | #78716c |
|
||||
| --text-h | #1c1917 |
|
||||
| --border | #e7e5e4 |
|
||||
| --accent | #f97316 (orange-500) |
|
||||
| --stripe | rgba(249, 115, 22, 0.03) |
|
||||
| Feel | Paper-like, warm, comfortable for long sessions |
|
||||
|
||||
---
|
||||
|
||||
## Theme Picker Dropdown
|
||||
|
||||
**OBJECT ID:** `theme-picker`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Trigger | Click theme icon in header or status bar, or Ctrl+Shift+T |
|
||||
| Position | Dropdown below header button, right-aligned |
|
||||
| Width | 240px |
|
||||
| Background | var(--bg) |
|
||||
| Border | 1px solid var(--border) |
|
||||
| Border radius | 8px |
|
||||
| Shadow | 0 4px 16px rgba(0, 0, 0, 0.15) |
|
||||
| Padding | space-xs (6px) |
|
||||
| Animation | Scale from 95% + fade, 150ms ease-out |
|
||||
| Dismiss | Click outside, Esc, or select theme |
|
||||
|
||||
### Theme Picker Layout
|
||||
|
||||
```
|
||||
┌──────────────────────────┐
|
||||
│ Themes │ section header
|
||||
│ │
|
||||
│ ☀️ Light ✓ │ active indicator
|
||||
│ 🌙 Dark │
|
||||
│ 💻 Matrix │
|
||||
│ 🌊 Midnight │
|
||||
│ 📜 Warm │
|
||||
│ │
|
||||
│ ────────────────────── │ separator
|
||||
│ │
|
||||
│ Accent Color │ section header
|
||||
│ [●][●][●][●][●][●][●] │ color swatches
|
||||
│ │
|
||||
│ ────────────────────── │
|
||||
│ ⚙️ System (auto) │ follows OS preference
|
||||
└──────────────────────────┘
|
||||
```
|
||||
|
||||
### Theme Item
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Height | 36px |
|
||||
| Padding | 8px 12px |
|
||||
| Layout | Icon + name + optional checkmark |
|
||||
| Hover | var(--accent-bg) background, border-radius 4px |
|
||||
| Active | Checkmark ✓ on right side, weight 500 |
|
||||
| Click | Apply theme instantly, close picker |
|
||||
| Preview | On hover, show a 4-color mini-swatch (bg, text, accent, secondary) |
|
||||
|
||||
### Color Swatch Preview (on hover)
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Size | 4 circles, 12px each, 4px gap |
|
||||
| Colors | bg, bg-secondary, accent, text — of the hovered theme |
|
||||
| Position | Inline after theme name |
|
||||
|
||||
---
|
||||
|
||||
## Accent Color Picker
|
||||
|
||||
**OBJECT ID:** `theme-accent-picker`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Location | Inside theme picker dropdown, below themes |
|
||||
| Presets | 7 color swatches in a row |
|
||||
| Swatch size | 20px circles, 6px gap |
|
||||
| Active swatch | 2px ring in var(--text-h) |
|
||||
| Custom | Last swatch is rainbow gradient → opens native color picker |
|
||||
| Behavior | Overrides --accent, --accent-bg, --accent-border for current theme |
|
||||
| Persistence | Stored as `accentColor` in localStorage |
|
||||
|
||||
### Preset Accent Colors
|
||||
|
||||
| Name | Value | Hex |
|
||||
|------|-------|-----|
|
||||
| Indigo (default) | Indigo 500/400 | #6366f1 / #818cf8 |
|
||||
| Teal | Teal 500/400 | #14b8a6 / #2dd4bf |
|
||||
| Rose | Rose 500/400 | #f43f5e / #fb7185 |
|
||||
| Amber | Amber 500/400 | #f59e0b / #fbbf24 |
|
||||
| Emerald | Emerald 500/400 | #10b981 / #34d399 |
|
||||
| Sky | Sky 500/400 | #0ea5e9 / #38bdf8 |
|
||||
| Custom | User pick | Via `<input type="color">` |
|
||||
|
||||
Each preset has light/dark variants. The correct variant is selected based on current base theme.
|
||||
|
||||
---
|
||||
|
||||
## System Theme Option
|
||||
|
||||
**OBJECT ID:** `theme-system`
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Behavior | Follows OS `prefers-color-scheme` |
|
||||
| Matches | Light ↔ Light theme, Dark ↔ Dark theme |
|
||||
| Override | Selecting any specific theme disables system following |
|
||||
| Re-enable | Select "System (auto)" in picker |
|
||||
| Label | "⚙️ System (auto)" with current resolved theme name |
|
||||
|
||||
---
|
||||
|
||||
## Theme Application Flow
|
||||
|
||||
```
|
||||
1. User clicks theme → store in localStorage
|
||||
2. Apply CSS class to <html>: `data-theme="matrix"`
|
||||
3. CSS variables resolve from theme class
|
||||
4. All components instantly update (CSS-only, no React re-render)
|
||||
5. CodeMirror theme needs manual reconfiguration (dispatch theme compartment)
|
||||
6. PWA theme-color meta tag updates for status bar color
|
||||
```
|
||||
|
||||
### CodeMirror Theme Sync
|
||||
|
||||
| Step | Action |
|
||||
|------|--------|
|
||||
| 1 | Theme changes → dispatch new baseTheme to EditorView |
|
||||
| 2 | Syntax highlighting colors update via CSS variables (no extension swap) |
|
||||
| 3 | Stripe colors update via CSS |
|
||||
| 4 | Active line highlight updates via CSS |
|
||||
| 5 | Only the base theme extension needs reconfiguration |
|
||||
|
||||
---
|
||||
|
||||
## Responsive
|
||||
|
||||
| Breakpoint | Theme Picker |
|
||||
|------------|-------------|
|
||||
| >= 768px | Dropdown panel below header button |
|
||||
| < 768px | Bottom sheet (slides up from bottom, 60vh max height) |
|
||||
|
||||
### Mobile Bottom Sheet
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Width | 100vw |
|
||||
| Max height | 60vh |
|
||||
| Border radius | 12px 12px 0 0 (top corners) |
|
||||
| Drag handle | 32px × 4px pill, centered |
|
||||
| Backdrop | 50% black overlay |
|
||||
| Animation | Slide up 200ms ease-out |
|
||||
| Close | Swipe down, tap backdrop |
|
||||
|
||||
---
|
||||
|
||||
## Page States
|
||||
|
||||
| State | When | Behavior |
|
||||
|-------|------|----------|
|
||||
| **First launch** | No theme preference | Follow OS (system). If OS is dark, use Dark. |
|
||||
| **Theme selected** | User picked a theme | Applied instantly, persisted. System mode disabled. |
|
||||
| **System mode** | User selected "System (auto)" | Listens to OS changes in real-time. |
|
||||
| **Custom accent** | User picked accent color | Overrides accent tokens. Works with any base theme. |
|
||||
| **Matrix active** | Matrix theme selected | Font override applied. Scanline effect enabled. Green cursor glow. |
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- **No Flash of Unstyled Content (FOUC):** Load theme from localStorage in `<script>` in `<head>` BEFORE React mounts. Set `data-theme` on `<html>` synchronously.
|
||||
- **CSS structure:** Each theme is a `[data-theme="name"]` selector block overriding `:root` variables.
|
||||
- **Matrix performance:** Scanline effect uses `pointer-events: none` and is pure CSS. No performance impact.
|
||||
- **Cross-platform:** macOS/Windows use native appearance APIs. Theme names map to native equivalents. Matrix theme = custom dark palette on all platforms.
|
||||
- **PWA:** Update `<meta name="theme-color">` dynamically when theme changes for native status bar color.
|
||||
|
||||
---
|
||||
|
||||
_Created using Whiteport Design Studio (WDS) methodology_
|
||||
@@ -0,0 +1,278 @@
|
||||
# 6.1 — Mobile Experience
|
||||
|
||||
**Previous Step:** ← [Theme System](../../05-theming/5.1-theme-system/5.1-theme-system.md)
|
||||
|
||||
---
|
||||
|
||||
## Page Metadata
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Scenario** | 06 — Mobile Experience |
|
||||
| **Page Number** | 6.1 |
|
||||
| **Platform** | Web (PWA) — mobile viewport |
|
||||
| **Page Type** | Responsive Adaptation (all app shell components) |
|
||||
| **Viewport** | < 768px |
|
||||
| **Interaction** | Touch-first |
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**Page Purpose:** Define how the entire CalcText workspace adapts to mobile. This is NOT a separate app — it's the same app intelligently restructured for touch and small screens. Every feature remains accessible; nothing is hidden or removed.
|
||||
|
||||
**Current Problem:** The existing web app hides results, toolbar, and divider on mobile — effectively removing the product's value on the most common device type.
|
||||
|
||||
**Success Criteria:**
|
||||
- All features accessible on mobile (calculations, results, file management, themes)
|
||||
- Touch targets >= 44px
|
||||
- One-handed operation possible for core flow (type → see result)
|
||||
- Feels like a native mobile app when installed as PWA
|
||||
|
||||
---
|
||||
|
||||
## Mobile Layout (< 768px)
|
||||
|
||||
```
|
||||
┌─────────────────────────────┐
|
||||
│ Header [≡] CalcText [🎨]│ 44px (touch-sized)
|
||||
├─────────────────────────────┤
|
||||
│ Tab Bar (horizontal scroll) │ 40px
|
||||
├─────────────────────────────┤
|
||||
│ │
|
||||
│ │
|
||||
│ Editor │
|
||||
│ (full width) │
|
||||
│ │
|
||||
│ │
|
||||
│ │
|
||||
├─────────────────────────────┤
|
||||
│ ═══ Results Tray │ 48px collapsed
|
||||
│ Last: $4,300 │
|
||||
├─────────────────────────────┤
|
||||
│ Status (simplified) │ 24px
|
||||
└─────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Adaptations
|
||||
|
||||
### Header (Mobile)
|
||||
|
||||
| Property | Desktop | Mobile |
|
||||
|----------|---------|--------|
|
||||
| Height | 40px | 44px (touch target) |
|
||||
| Left content | Logo + "CalcText" | [≡] hamburger + "CalcText" |
|
||||
| Right content | Theme + Settings + ⌘ | [🎨] theme only |
|
||||
| Padding | 12px 12px | 8px 12px |
|
||||
| Hamburger | N/A | 44px × 44px touch target, opens sidebar drawer |
|
||||
|
||||
### Tab Bar (Mobile)
|
||||
|
||||
| Property | Desktop | Mobile |
|
||||
|----------|---------|--------|
|
||||
| Height | 36px | 40px |
|
||||
| Tab min width | 100px | 80px (more compact) |
|
||||
| Close button | Visible on hover/active | Hidden — swipe left to reveal |
|
||||
| New tab (+) | After last tab | Sticky far-right |
|
||||
| Scroll | Mouse wheel | Touch swipe horizontal |
|
||||
| Active indicator | Top 2px accent border | Bottom 2px accent border (thumb reachable) |
|
||||
|
||||
### Editor (Mobile)
|
||||
|
||||
| Property | Desktop | Mobile |
|
||||
|----------|---------|--------|
|
||||
| Width | Flex (shared with results) | 100vw |
|
||||
| Line padding | 12px | 12px |
|
||||
| Gutter | 40px (line numbers) | 32px (compact numbers) |
|
||||
| Font size | 15px | 15px (same — readable on mobile) |
|
||||
| Line height | 24px | 24px (same) |
|
||||
| Keyboard | Physical | Virtual — editor scrolls above keyboard |
|
||||
|
||||
#### Virtual Keyboard Handling
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Viewport | Uses `100dvh` (dynamic viewport height) to account for keyboard |
|
||||
| Scroll | Editor auto-scrolls to keep cursor visible above keyboard |
|
||||
| Results tray | Hides when keyboard is open (not enough space) |
|
||||
| Status bar | Hides when keyboard is open |
|
||||
|
||||
### Results Tray (Mobile)
|
||||
|
||||
Replaces the side panel with a bottom tray.
|
||||
|
||||
| State | Height | Content |
|
||||
|-------|--------|---------|
|
||||
| **Collapsed** | 48px | Drag handle + last non-empty result |
|
||||
| **Expanded** | 40vh (max 60vh) | Full scrollable results list |
|
||||
| **Hidden** | 0px | When virtual keyboard is open |
|
||||
|
||||
#### Collapsed Tray
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Background | var(--bg-secondary) |
|
||||
| Border top | 1px solid var(--border) |
|
||||
| Drag handle | 32px × 4px pill, var(--border), centered |
|
||||
| Content | "Last: {value}" — last non-empty result, text-xs |
|
||||
| Tap | Expand tray |
|
||||
| Swipe up | Expand tray |
|
||||
|
||||
#### Expanded Tray
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Content | All results paired with expressions |
|
||||
| Item format | Line number + expression snippet + result value |
|
||||
| Item height | 44px (touch-friendly) |
|
||||
| Active line | var(--accent-bg) highlight |
|
||||
| Tap result | Copy to clipboard + brief feedback |
|
||||
| Swipe down | Collapse tray |
|
||||
| Tap drag handle | Collapse |
|
||||
| Scroll | Independent vertical scroll |
|
||||
|
||||
#### Tray Interaction
|
||||
|
||||
```
|
||||
┌──────────────────────────────┐
|
||||
│ ═══ (drag handle) │ Swipe up to expand
|
||||
│ Last: $4,300 │
|
||||
├──────────────────────────────┤ ← expanded state below
|
||||
│ 5 salary 5,000 │
|
||||
│ 6 freelance 1,200 │
|
||||
│ 7 total_income 6,200 │ ← highlighted (active line)
|
||||
│ 9 rent 1,500 │
|
||||
│ 10 groceries 400 │
|
||||
│ 13 total_expenses 1,900 │
|
||||
│ 14 savings 4,300 │
|
||||
└──────────────────────────────┘
|
||||
```
|
||||
|
||||
### Sidebar Drawer (Mobile)
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Trigger | Hamburger [≡] in header |
|
||||
| Width | 85vw, max 320px |
|
||||
| Position | Fixed left, full height |
|
||||
| Background | var(--bg) |
|
||||
| Backdrop | rgba(0, 0, 0, 0.5) — tap to close |
|
||||
| Animation | translateX(-100%) → translateX(0), 200ms ease-out |
|
||||
| Close | Tap backdrop, swipe left, × button (top-right, 44px target) |
|
||||
| Content | Same sections as desktop sidebar (search, recent, favorites, files, templates) |
|
||||
| File tap | Opens document → auto-closes drawer |
|
||||
| Search | Full width, 44px height (touch target) |
|
||||
| File items | 44px height (touch target, up from 28px desktop) |
|
||||
|
||||
### Theme Picker (Mobile)
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Trigger | [🎨] button in header |
|
||||
| Style | Bottom sheet (slides from bottom) |
|
||||
| Height | Auto (content-driven), max 60vh |
|
||||
| Border radius | 12px 12px 0 0 |
|
||||
| Drag handle | 32px × 4px pill, centered |
|
||||
| Items | 48px height per theme (touch targets) |
|
||||
| Accent swatches | 32px circles, 8px gap (larger for touch) |
|
||||
| Close | Swipe down, tap backdrop |
|
||||
|
||||
### Status Bar (Mobile)
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Height | 24px (same) |
|
||||
| Content | Cursor position + engine status dot only |
|
||||
| Hidden items | Line count, theme indicator (accessible elsewhere) |
|
||||
| Hidden | When virtual keyboard is open |
|
||||
|
||||
---
|
||||
|
||||
## Touch Gestures
|
||||
|
||||
| Gesture | Context | Action |
|
||||
|---------|---------|--------|
|
||||
| Swipe left on tab | Tab bar | Reveal close button |
|
||||
| Swipe up on results tray | Results | Expand tray |
|
||||
| Swipe down on results tray | Results | Collapse tray |
|
||||
| Swipe left from right edge | Sidebar drawer | Close drawer |
|
||||
| Swipe down on bottom sheet | Theme picker | Close sheet |
|
||||
| Long press on file | Sidebar | Show context menu |
|
||||
| Long press on tab | Tab bar | Drag to reorder |
|
||||
| Tap result | Results tray | Copy to clipboard |
|
||||
| Pinch | Editor | Zoom font size (optional) |
|
||||
|
||||
---
|
||||
|
||||
## Breakpoint Details
|
||||
|
||||
| Width | Classification | Key Adaptations |
|
||||
|-------|---------------|-----------------|
|
||||
| **>= 1024px** | Desktop | Full 3-panel layout |
|
||||
| **768–1023px** | Tablet | Sidebar → overlay drawer. Editor + results side-by-side. |
|
||||
| **480–767px** | Mobile | Single column. Results tray. Sidebar drawer. Touch targets 44px. |
|
||||
| **< 480px** | Small mobile | Same as mobile. Tab labels may truncate. Logo text hidden. |
|
||||
|
||||
### Tablet Specifics (768–1023px)
|
||||
|
||||
| Component | Behavior |
|
||||
|-----------|----------|
|
||||
| Sidebar | Overlay drawer (hamburger toggle) instead of persistent panel |
|
||||
| Editor + Results | Side-by-side with divider (same as desktop) |
|
||||
| Tab bar | Same as desktop (enough width) |
|
||||
| Header | Show hamburger [≡] instead of persistent sidebar |
|
||||
| Theme picker | Dropdown (same as desktop) |
|
||||
|
||||
---
|
||||
|
||||
## PWA Mobile Enhancements
|
||||
|
||||
| Feature | Implementation |
|
||||
|---------|---------------|
|
||||
| Standalone display | `display: standalone` — no browser chrome |
|
||||
| Status bar color | `theme-color` meta tag updates per theme |
|
||||
| Safe areas | `env(safe-area-inset-*)` for notched devices |
|
||||
| Splash screen | Theme-colored background + CalcText logo |
|
||||
| Home screen icon | App icon with accent color ring |
|
||||
| Orientation | Portrait preferred, landscape supported |
|
||||
|
||||
### Safe Area Padding
|
||||
|
||||
```css
|
||||
.calcpad-app {
|
||||
padding-top: env(safe-area-inset-top);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Page States (Mobile-Specific)
|
||||
|
||||
| State | When | Behavior |
|
||||
|-------|------|----------|
|
||||
| **Keyboard open** | User tapped editor | Results tray + status bar hide. Editor fills available space. |
|
||||
| **Keyboard closed** | User tapped outside or pressed Done | Results tray + status bar reappear. |
|
||||
| **Drawer open** | Hamburger tapped | Sidebar overlays. Backdrop captures tap to close. |
|
||||
| **Tray expanded** | User swiped up | 40vh results list. Editor partially visible above. |
|
||||
| **Offline** | No network | Status bar shows offline indicator. All features work. |
|
||||
|
||||
---
|
||||
|
||||
## Technical Notes
|
||||
|
||||
- **Viewport units:** Use `dvh` (dynamic viewport height) not `vh` to handle mobile browser chrome and virtual keyboard.
|
||||
- **Touch events:** Use `touchstart`/`touchmove`/`touchend` for swipe gestures. Consider `passive: true` for scroll performance.
|
||||
- **Overscroll:** Disable `overscroll-behavior: none` on app container to prevent pull-to-refresh interference.
|
||||
- **iOS safe areas:** Test with iPhone notch and dynamic island. Apply `env(safe-area-inset-*)`.
|
||||
- **Android back button:** In PWA mode, back button should close drawers/sheets before navigating back.
|
||||
- **Font scaling:** Respect system font size on mobile. Use relative units where possible.
|
||||
- **Cross-platform:** Mobile web layout does NOT need to port to native mobile (that's a separate app). But interaction patterns (swipe, long-press) inform native mobile design if built later.
|
||||
|
||||
---
|
||||
|
||||
_Created using Whiteport Design Studio (WDS) methodology_
|
||||
Reference in New Issue
Block a user