Files
svsmspcalc/pre-alpha/docs/CHECKPOINT.md
2026-03-16 01:42:17 -04:00

23 KiB
Raw Permalink Blame History

SVS MSP CALC — Beta Build Checkpoint

Date: 2026-03-15 Status: Phases 18 + Stage 8 complete. Beta + a11y/perf audit + code quality passes I & II + test expansion + print enhancements done. Tests: 254/254 passing Build Prompt: .claude/plans/STAGE2-BUILD-PROMPT.md Previous Stage Prompt: docs/STAGE3-SESSION-PROMPT.md Previous Stage Prompt: docs/STAGE5-SESSION-PROMPT.md Previous Stage Prompt: docs/STAGE6-SESSION-PROMPT.md Previous Stage Prompt: docs/STAGE7-SESSION-PROMPT.md Previous Stage Prompt: docs/STAGE8-SESSION-PROMPT.md


Completed

Phase 1: Bug Fixes (6/6)

# Issue File Change
1.1 ADDON_INKY default $5 → $8 quote-pricing.js:12 ADDON_INKY: 58
1.2 Onboarding fee loses manual override on term switch SVS-MSP-Calculator.js:41-70 Store manual value in data-manual-value before 24mo clears it; restore on switch back to m2m/12mo
1.3 VoIP fax CSV comment misleading package-prices.csv:18 "Flat/mo" → "Per seat/mo"
1.4 Print forces HST on regardless of user toggle quote-export.js:12 Removed state.hstEnabled = true; — print now respects user's HST toggle
1.5 JSON export missing schema version quote-export.js:229 Added version: '1.0' as first field in payload
1.6 ZT admin supplement triggers with no warning quote-render.js:494-499 New amber nudge when ztActive warns about $250 admin supplement

Test expectations updated in test-quote-engine.js for INKY $8 (4 values changed).

Phase 2: Visual Polish (Sections IIII)

# Issue File Change
2.1a Hardcoded #e06070 danger icon components.css:41 var(--text-danger) — adapts per theme
2.1b Hardcoded #86efac pill-savings on checked state components.css:442 var(--text-pill-savings-active) — new token
Token added to all 4 themes tokens.css, light.css, glass.css, 70retro.css Dark: #86efac, Light: #d4f5e0, Glass: #a8f0c8, Retro: #e0f0d0
2.1c QUICK-REF.md outdated docs/QUICK-REF.md Updated INKY $8, export desc, theme list, test count

Audit findings (no action needed):

  • All 4 themes fully token-covered for Sections IIII
  • Glass theme uses !important selector overrides (valid for glassmorphism effects)
  • Sidebar focus-toggle white rgba values sit on colored header — correct everywhere
  • No remaining hardcoded colors in Sections IIII component CSS
  • Sidebar renders correctly across all 4 themes at all breakpoints

Phase 3: UX Hardening (Sections IIII)

3.1 Interaction Refinements

# Change Files Details
3.1a Smooth theme-switch transition tokens.css, theme-manager.js body.theme-transitioning class enables 0.25s color/bg/border fade; applied for 300ms during toggleTheme()
3.1b Nudge crossfade on rotation/nav components.css, quote-render.js .nudge-fading class fades opacity to 0; cycleNudge() does fade-out → swap → fade-in (180ms); auto-rotation now uses cycleNudge(1) for consistency
3.1c Summary badge fade-in on collapse components.css @keyframes badgeFadeIn — 0.25s opacity + translateY animation on .sec-summary-badge
3.1d Addon toggle micro-feedback components.css @keyframes addonPulse — 0.2s scale(1.015) pulse on .addon-row.selected

3.2 Responsive Edge Cases

# Change Files Details
3.2a Touch targets ≥44px on mobile responsive.css .mobile-panel-close-btn 36→44px, .nudge-nav-btn 34→44px at ≤1100px; .collapsible-header and .section-toggle min-height 44px at ≤600px
3.2b Container query fallback verified @container (max-width: 760px) for addon rows has adequate fallback via ≤600px media query; no change needed

3.3 Mobile Experience Completeness

# Change Files Details
3.3a Focus trap in mobile panel mobile-sync.js trapFocus() function keeps Tab cycling within open panel; focus moves to close button on open, returns to pill on close
3.3b Safe-area insets for notch phones responsive.css padding-bottom: env(safe-area-inset-bottom) on .mobile-panel-sheet; right: max(14px, env(safe-area-inset-right)) on .mobile-quote-pill

GATE: 88/88 tests pass. All JS syntax-checked. CSS brace balance verified.


Phase 4: Documentation & QA

# Task Status
4.1 Update all docs (README, code-verification, quote-rules, phase-roadmap, QUICK-REF, MASTER-SESSION-PROMPT, ai-session-brief) COMPLETE
4.2 Full regression checklist walkthrough COMPLETE — 88/88 automated, 15/15 manual items verified in code
4.3 Beta definition of done verification COMPLETE — all 13 criteria pass

Docs updated:

  • README.md — phase status, 88 tests, 4 themes, export description, file map (70retro.css added)
  • code-verification.md — date, test count, all Phase 1-3 changes as known-good baseline
  • quote-rules.md — onboarding manual override persistence, HST print behavior, JSON export rules, admin nudge
  • phase-roadmap.md — Phases 1-4 status, 88 tests
  • QUICK-REF.md — test count in "Remind User", 70retro.css in CSS file map
  • MASTER-SESSION-PROMPT.md — 88 tests (3 occurrences), 4 themes (6 occurrences), 70retro.css in tree, 4 theme override layers
  • ai-session-brief.md — test count updated

Regression checklist results:

  • Automated: 88/88 pass
  • Manual: All 15 items verified via source code review (admin waive displays, term/onboarding logic, manual override persistence, sidebar sync, mobile panel sync, persistence round-trip, reset behavior, print HST, JSON export, section headers, theme transitions, nudge crossfade, focus trap, safe-area insets, touch targets)

Beta Definition of Done: ALL 13 CRITERIA PASS

GATE: PASSED — Beta build for Sections IIII is complete.


Phase 5: Performance & Accessibility Audit

# Fix File(s) Details
A2 aria-expanded on section & collapsible toggles HTML, SVS-MSP-Calculator.js Added aria-expanded="false" to 12 toggle elements; JS updates dynamically on toggle
A3 Focus trap on reset confirm modal quote-persistence.js trapFocusInModal() — Tab cycles within modal when open
A4 aria-label on stepper buttons HTML All 12 step-btn elements have descriptive labels (e.g. "Decrease users")
P1 Glass theme scroll jank on mobile glass.css background-attachment: scroll at ≤1100px — avoids fixed-bg repaint on iOS
P2 Skip mobile sync on desktop mobile-sync.js Guard skips 35+ element sync when panel closed on desktop; forces full sync on openMobilePanel()
M1 sidebarFocusClientName not in sync map mobile-sync.js Added to html sync list — client name now updates in mobile panel
M2 sl-discount-detail + sl-value-onboarding-label not in sync map mobile-sync.js Added to html sync list — contract term label and onboarding label now sync

Not flagged (clean): Token coverage, :focus-visible, mobile focus trap, escape handling, touch targets, will-change usage, print CSS isolation, no unused JS.

GATE: 88/88 tests pass. All fixes verified.

Font Awesome Icon Fix

# Fix File Details
FA1 Icons invisible on file:// protocol components.css:44-79 All 36 FA Sharp Solid SVG file references converted to inline data:image/svg+xml URIs — eliminates CORS/file:// restriction on mask-image: url()

Root cause: CSS mask-image: url("fontawesomekit/svgs/...") is blocked by browser security on the file:// protocol. Inline data URIs bypass this completely.

GATE: 88/88 tests pass. Icons render on local file open.


Phase 6: Code Quality Pass (Stage 3)

# Fix File(s) Details
CQ1 New --sky color token tokens.css, light.css, glass.css, 70retro.css Per-theme sky/info accent: Dark #38bdf8, Light #0e7490, Glass #7dd3fc, Retro #a34a14
CQ2 New --transition-fast token tokens.css 0.15s — replaces hardcoded timing in layout.css button transitions
CQ3 Consolidated duplicate button CSS layout.css:25-47 .btn-reset-quote and .btn-import-quote shared 10 identical properties → merged into grouped selector
CQ4 Hardcoded amber hover → token-derived layout.css:43-46 rgba(232,146,15,…)color-mix(in srgb, var(--amber) …%, transparent)
CQ5 Hardcoded sky blue hover → token-derived layout.css:47-50, light.css, glass.css, 70retro.css All rgba(56,189,248,…) / #38bdf8 / #7dd3fc / #a34a14var(--sky) + color-mix()
CQ6 Dead null-check removed quote-render.js:533 `nudgeIndex == null

Audit findings (no action taken — documented for future):

  • fmt() duplicated in quote-render.js and quote-export.js (both inside IIFEs — intentional isolation, one-liner)
  • Spacing magic numbers (14px/16px/20px) used 95+ times — too many touchpoints for surgical migration
  • console.warn() statements in pricing/persistence/import are intentional error reporting
  • No dead functions, no unreachable code, no unused exports across all 8 JS modules

GATE: 88/88 tests pass. All 4 themes verified tokenized.


Phase 7: Test Coverage Expansion (Stage 4)

# Test Group Count Details
T1 Pricing DEFAULTS integrity 34 All required keys exist, types correct, values match spec, frozen, ordering invariants
T2 Engine edge cases & boundaries 55 Admin fee thresholds, large counts (100u/100ep), string coercion, invalid inputs (NaN/null/empty), servers-only, VoIP-only, VoIP edge cases, ZT without user addon, admin waived, all addons combined, BYOL term independence, discount rounding
T3 Export JSON schema validation 18 Payload structure, field types, version field, contract term labels, licensing labels, pricing sub-object, voip tier null handling
T4 Persistence state shape 6 JSON round-trip for strings/numbers/booleans, engine compatibility, zero-state
T5 Import payload mapping 12 Contract term reverse-map, full export→import→engine round-trip (MRR, effectiveMrr, mrrWithHst, userTotal, endpointTotal, voipTotal, adminFeeNet, effectiveAnnual)
T6 Quote output invariants 24 6 configs × 4 invariants (effectiveMrr, effectiveAnnual, mrrWithHst, non-negative values)

Total: 88 → 250 tests (162 new). All passing.

GATE: 250/250 tests pass.


Phase 8: Enhanced Print/PDF (Stage 4)

# Enhancement File(s) Details
P1 Quote notes field HTML:920, components.css, quote-persistence.js, quote-export.js, quote-import.js <textarea id="quoteNotes"> in sidebar, persisted in localStorage, included in JSON export/import, rendered on print invoice
P2 Explicit validity date quote-export.js Computes 30-day expiry: "Valid until [date]" in print footer instead of generic "30 days"
P3 Page break control quote-export.js (inline CSS) page-break-inside:avoid on table rows + .tots-wrap; break-inside:avoid on notes section
P4 Rep name field HTML:100, layout.css, quote-persistence.js, quote-export.js, quote-import.js <input id="repName"> below client name, persisted, in JSON export/import, shown in print header + footer
P5 CYA "Not Included" section quote-export.js Print splits config into "Your Service Configuration" (active) + "Services Not Included in This Quote" (excluded, muted, smaller)

Additional changes:

  • JSON export schema version bumped to 1.1 (new repName, quoteNotes fields)
  • JSON import handles new fields gracefully (backward-compatible with 1.0 exports)
  • Print CSS hides notes + rep inputs on @media print (main page path)
  • 4 new tests added (repName/quoteNotes in export schema + persistence)

GATE: 254/254 tests pass.


Key Files to Read on Resume

  1. docs/MASTER-SESSION-PROMPT.md — full architecture and constraints
  2. docs/QUICK-REF.md — compact file map, IDs, pricing
  3. docs/regression-checklist.md — test procedures
  4. .claude/plans/STAGE2-BUILD-PROMPT.md — the build prompt driving this work
  5. This file — checkpoint status

Stage 5 / Phase 9: Visual QA + Retro Theme Overhaul

Visual QA: 3 breakpoints (mobile ~375px, desktop ~1100-1400px, wide ~1800px+) × 4 themes.

Theme Mobile Desktop Wide Result
Dark Clean Clean Clean PASS
Light Clean Clean Clean PASS
Glass Clean Clean Clean PASS
Retro Overhauled REWORKED

Retro theme overhaul:

  • Problem: Original 70s wood-panel brown palette had low contrast, muddy colors, invisible logo (black SVG on brown header)
  • Solution: Warm paper base + neon-warm cyberpunk accents
  • Accent: hot rose #e11d48 (warm neon, harmonizes with cream)
  • Green/Sky: warm teal #0d9488
  • Header: warm charcoal #1c1317 with rose neon border
  • Logo: .top-bar-logo path { fill: #f0e4d0 } — overrides hardcoded #0c0c0c SVG fills
  • Progress bar: rose → teal gradient
  • Paper texture: warm brown scanlines (unchanged from original)
  • Status: Functional, user notes full design pass deferred to later

Remaining QA not yet done: Retro theme at all viewport widths, landscape orientation.

GATE: 254/254 tests pass. No visual bugs found on Dark/Light/Glass.


Stage 6 / Phase 10: Elastic Responsive Foundation

Problem: 5 fixed breakpoints (1350, 1100, 900, 600, 780px landscape) with hardcoded px overrides at each step. Max width capped at 1800px — wasted space on 1440p+ monitors.

Solution: Fluid clamp() tokens replace discrete breakpoint steps. Only structural breakpoints remain.

# Change File(s) Details
E1 Fluid layout tokens tokens.css --page-max-width: clamp(1200px, 92vw, 2400px), --page-gutter-x: clamp(16px, 3vw, 80px), --layout-column-gap: clamp(24px, 3vw, 56px), sidebar min 400→360px
E2 Fluid section tokens tokens.css --section-offset: clamp(52px, 7vw, 104px), --section-num-width/size fluid, --section-padding-* fluid
E3 Eliminated 1350px breakpoint responsive.css Removed — fluid tokens handle narrow desktop scaling
E4 Eliminated 900px breakpoint responsive.css Removed — fluid tokens handle tablet spacing/numerals
E5 Fluid logo margin base.css margin-left: clamp(26px, 5.2vw, 78px) replaces hardcoded 78px + breakpoint overrides
E6 Fluid main-col gap layout.css gap: clamp(16px, 1.5vw, 24px) replaces hardcoded 24px + breakpoint override
E7 Fluid client-bar padding layout.css clamp() on vertical padding, var(--section-offset) for left

Breakpoint reduction: 5 → 3 (1100px structural, 600px phone layout, 780px landscape orientation)

Width scaling:

  • 1080p (1920px): content fills ~1766px (92vw)
  • 1440p (2560px): content fills ~2355px (92vw)
  • 4K (3840px): content caps at 2400px max

GATE: 254/254 tests pass.


Stage 7 / Phase 11: Feature Work (Option A)

11.1 Keyboard Shortcuts

Shortcut Action File Details
Ctrl+P Print invoice SVS-MSP-Calculator.js preventDefault() blocks browser print dialog; calls printInvoice()
Ctrl+E Export JSON SVS-MSP-Calculator.js Calls exportQuoteJSON()
Ctrl+R Reset quote SVS-MSP-Calculator.js Opens confirm modal via openResetConfirm() — not a hard reset
Escape Close overlays mobile-sync.js (existing) Already handled — closes sidebar focus + mobile panel

All shortcuts are suppressed when focus is in an <input>, <textarea>, or <select> to avoid hijacking normal typing.

11.2 New Contextual Nudges

# Nudge Color Trigger
N1 Users set but no endpoints amber users > 0 && endpoints === 0
N2 VoIP seats ≠ user count amber voipSeats > 0 && users > 0 && voipSeats !== users
N3 High admin-to-MRR ratio amber adminFeeNet > MRR * 0.25 (and not waived)
N4 Extended Hours upsell green !addExtHours && users > 0

Added after existing nudges in buildNudges() in quote-render.js (lines 524551).

GATE: 254/254 tests pass.


Stage 8 / Phase 12: Code Quality Pass II

8.1 --transition-fast / --transition-medium Token Adoption

# Change File(s) Details
T1 New --transition-medium token tokens.css 0.25s — for chevron/collapsible/nudge transforms
T2 10× 0.15svar(--transition-fast) components.css pill-toggle, tier-seg, addon-preview-pill, addon checkbox, sidebar-focus-toggle, nudge-nav-btn, btn-toggle-all, quote-notes-input, btn-export
T3 3× 0.25svar(--transition-medium) components.css sec-chevron transform, collapsible-toggle transform, nudge-banner bg/border
T4 1× 0.15svar(--transition-fast) base.css theme-toggle-btn
T5 2× 0.15svar(--transition-fast) responsive.css mobile-quote-pill, mobile-panel-close-btn

Left as-is: 0.12s (stepper/addon micro-interactions), 0.18s (term tile tuned), 0.2s (switch/section/overlay), 0.3s (progress bar/accordion), 0.34s (section-body tuned bezier). No 0.15s hardcodes remain outside the token definition.

8.2 CSS Selector Specificity Audit

# Change File(s) Details
S1 .sec-open.section.sec-open components.css Removed 2× !important — specificity now beats .section:hover via class count
S2 Documented intentional !important components.css Added comments to .qs-discount-sub, sidebar utility classes (.sl-muted, .sl-discount-val, .sl-hst-val), and VS value classes

Audit findings (no action — all legitimate):

  • components.css: 13 remaining !important — all utility display: none or color overrides that must beat compound parent selectors
  • 70retro.css: 37 !important — theme override pattern (same as glass.css with 97)
  • responsive.css: 8 !important — mobile sidebar embedding
  • tokens.css: 1 !importantbody.theme-transitioning (intentional, per spec)
  • print.css: All !important — standard @media print override pattern
  • No overly-qualified selectors found (element-qualified patterns are all necessary)

8.3 Print CSS Hardening

# Change File(s) Details
P1 Hide 4 missing interactive elements print.css Added display: none !important for .sidebar-focus-toggle, .sidebar-utility, .qs-switch, .confirm-modal
P2 Theme-independent callout borders tokens.css, print.css New --print-callout-green-border and --print-callout-red-border tokens replace theme-variable var(--green) and var(--surface-danger-border) in print context

Verification:

  • All --print-* tokens defined only in :root (tokens.css) — no theme overrides
  • Page-break rules unaffected by fluid layout tokens (.outer forced to display: block; max-width: 100% in print)
  • Print invoice (separate window) uses inline CSS — not affected by main page changes

8.4 (Stretch) Spacing Token Consolidation — Deferred

Assessment: 150+ magic-number spacing values across components.css (10px: 36, 12px: 35, 14px: 36, 16px: 24, 20px: 19). Existing --space-stack-* tokens used only 4× out of 150+. Migration scope too broad for surgical approach. Deferred to a dedicated spacing-focused stage.

GATE: 254/254 tests pass.


Stage 8 Feature Fixes

F1: Fullscreen Live Quote View — Print Only

# Change File(s) Details
F1a Hide Reset + Import in focus mode components.css .export-wrap and .sidebar-utility now display: none in sidebar-focus-open
F1b Print button inside sidebar header HTML, components.css New .sidebar-focus-print-btn in .sidebar-header-row — hidden by default, display: inline-flex in focus mode
F1c Print button hidden in print/mobile print.css, components.css display: none !important in @media print and .mobile-panel-sheet

Before: Focus mode hid Print/Export JSON, showed Reset/Import After: Focus mode shows a Print button in the header bar (next to collapse icon), hides all other action buttons

F2: Toggle Switch 2-State Theme Colors

# Change File(s) Details
F2a New --surface-switch-off / --surface-switch-on tokens tokens.css Dark: off #4a4540, on var(--green)
F2b Light theme switch tokens light.css Off #b5ad9f, on var(--green)
F2c Glass theme switch tokens glass.css Off rgba(255,255,255,0.15), on var(--green)
F2d Retro theme switch tokens 70retro.css Off #c0b4a0, on var(--green)
F2e Component CSS uses tokens components.css .qs-switch bg → var(--surface-switch-off), checked → var(--surface-switch-on)
F2f Glass checked override glass.css Added .qs-toggle-row input:checked ~ .qs-switch { background: var(--surface-switch-on) }

Before: Off = --border (barely visible), On = --accent (theme accent) After: Off = distinct muted track per theme, On = --green (universally "enabled")

GATE: 254/254 tests pass.

F1 Fix: Print Button Visibility in Focus Mode

# Change File(s) Details
F1d Print button inside sidebar header HTML:697 New .sidebar-focus-print-btn button in .sidebar-header-row, between title and collapse icon
F1e Focus-only visibility components.css display: none by default; display: inline-flex when body.sidebar-focus-open
F1f Hidden in print + mobile print.css, components.css display: none !important in @media print and .mobile-panel-sheet

Root cause: .sidebar-utility is a sibling of .sidebar, not inside it. When .sidebar becomes position: fixed, the utility div is left behind the backdrop.

F3: Pricing CSV → JSON Migration

# Change File(s) Details
F3a New JSON pricing file package-prices.json Structured by category with { key: { value, description } } format — human-readable + machine-parseable
F3b Script-loaded pricing package-prices-data.js, HTML window.SVS_PRICING_DATA set via <script> tag — works on file:// protocol, no web server needed
F3c Loader updated quote-pricing.js loadPricing() checks SVS_PRICING_DATA global first (script path), then fetch() fallback (web server), then built-in defaults
F3d CSV retained package-prices.csv Original CSV kept for reference; no longer loaded at runtime

How to update pricing: Edit package-prices-data.js — change the value field for any key. No web server needed. The file is loaded via <script> tag before the pricing engine initializes.

JSON format example:

{
  "user_packages": {
    "RATE_M365": { "value": 130, "description": "Per-user/mo rate — M365 included" }
  }
}

GATE: 254/254 tests pass.


Hard Constraints (reminder)

  1. DOM IDs are a contract — no renaming
  2. 254 tests must pass: node svsmspcalc/tests/test-quote-engine.js
  3. localStorage keys unchanged
  4. All 4 themes must work after every change
  5. Mobile parity maintained
  6. No frameworks, no npm — vanilla only
  7. Surgical changes only
  8. Sections IVVI unchanged (deferred)