Compare commits
3 Commits
5debac3aa8
...
de1e0c0847
| Author | SHA1 | Date | |
|---|---|---|---|
| de1e0c0847 | |||
| 4c53af96a9 | |||
| 9ba2bcf045 |
5
.claude/settings.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"enabledPlugins": {
|
||||||
|
"context-mode@context-mode": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,22 @@
|
|||||||
"Bash(git checkout*)",
|
"Bash(git checkout*)",
|
||||||
"Read(//c/Users/JohnOReilly/OneDrive - Silicon Valley Services/Documents/Projects VS CODE/SVS MSP Calculator/**)",
|
"Read(//c/Users/JohnOReilly/OneDrive - Silicon Valley Services/Documents/Projects VS CODE/SVS MSP Calculator/**)",
|
||||||
"mcp__plugin_playwright_playwright__browser_evaluate",
|
"mcp__plugin_playwright_playwright__browser_evaluate",
|
||||||
"mcp__plugin_playwright_playwright__browser_run_code"
|
"mcp__plugin_playwright_playwright__browser_run_code",
|
||||||
|
"Bash(node:*)",
|
||||||
|
"Bash(bun --version)",
|
||||||
|
"Read(//c/Users/JohnOReilly/AppData/Roaming/npm/node_modules/bun/**)",
|
||||||
|
"Read(//c/Users/JohnOReilly/**)",
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_doctor",
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_fetch_and_index",
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_execute_file",
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_execute",
|
||||||
|
"mcp__plugin_playwright_playwright__browser_take_screenshot",
|
||||||
|
"mcp__plugin_playwright_playwright__browser_click",
|
||||||
|
"mcp__plugin_accesslint_accesslint__analyze_color_pair"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"enabledPlugins": {
|
||||||
|
"context-mode@context-mode": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
.claude/skills/ui-ux-pro-max
Submodule
8
.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"accesslint": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@accesslint/mcp"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
118
.playwright-mcp/console-2026-03-17T15-34-42-889Z.log
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
[ 1258ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Experiment "944963740" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1262ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4011688770" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1305ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1957563541" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1317ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "831823566" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1317ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3950660279" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1318ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1758722097" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1318ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2205197867" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1322ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2928249761" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1322ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3959994305" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1330ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2042715633" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1330ms] [WARNING] WARN [Statsig] The user does not have the required id_type "workspace_id" for Gate "1627064618" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1331ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "463092697" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1332ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2055431672" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1332ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "1001765573" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1332ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3933797510" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1333ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "112877228" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1333ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Experiment "4220976262" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1335ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "544603680" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1336ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "733205176" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1338ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4100765009" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1339ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1542198993" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1340ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1053331630" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1341ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2335877601" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1343ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2400167019" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1343ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "507055138" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1344ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1946731762" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1371ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3530939117" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1374ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1829305865" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1376ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3605919906" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1376ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2689066271" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1377ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "735368229" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1378ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "3317473948" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1379ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3922476776" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1380ms] [WARNING] WARN [Statsig] The user does not have the required id_type "workspace_id" for Gate "1355972088" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1380ms] [WARNING] WARN [Statsig] The user does not have the required id_type "workspace_id" for Dynamic config "3553340571" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1385ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "988697485" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1402ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "491279851" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1402ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3999836663" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1404ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "224248639" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1406ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1524046265" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1406ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2688889402" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1408ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "757783264" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1409ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "713974087" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1409ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3194776735" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1409ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2064584210" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1409ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1791965523" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1411ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3871256114" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1418ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Experiment "876391302" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1418ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4164414103" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1424ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2322626856" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1428ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "745511604" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1428ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "616577762" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1437ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "1760795888" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1443ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1611573287" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1443ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4262905012" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1445ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3702009723" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1447ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3018147683" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1447ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4105134853" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1452ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "1535193150" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1456ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3602298601" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1456ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1909635392" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1460ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "3861898381" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1461ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4237780970" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1465ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4233029563" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1465ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "597299768" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1475ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4087025484" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1476ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "537200474" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1480ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "3401870206" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1484ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1860647109" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1484ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2502169545" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1492ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "3635154224" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1493ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1605274664" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1501ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3138376546" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1502ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3457598265" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1503ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "121411412" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1509ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3207737252" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1509ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2632917233" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1510ms] [WARNING] WARN [Statsig] The user does not have the required id_type "workspace_id" for Dynamic config "1967546325" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1511ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3955187385" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1511ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "278871159" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1515ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1768937626" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1526ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3014776572" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1526ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "46455729" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1527ms] [WARNING] WARN [Statsig] The user does not have the required id_type "workspace_id" for Experiment "3058946295" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1551ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2461452859" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1551ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2360528850" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1551ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "71872232" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1555ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3240576626" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1557ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2304807207" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1557ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "878458344" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1558ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "2751188136" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1558ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2095603192" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1559ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "916292397" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1559ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "58496621" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1564ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "69563813" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1565ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1599781686" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1570ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1945318446" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1573ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1468311859" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1574ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2053937752" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1574ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3664702598" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1574ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "471233253" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1574ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4012000346" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1577ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Dynamic config "2943229081" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1578ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2209667701" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1603ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1627380539" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1630ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Experiment "3203804112" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1675ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "4290238484" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1676ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "473637331" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1780ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2341940718" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1858ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3109853659" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1859ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3375735072" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1871ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "1920889267" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1871ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "2042610337" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1919ms] [WARNING] [GSI_LOGGER]: Your client application uses one of the Google One Tap prompt UI status methods that may stop functioning when FedCM becomes mandatory. Refer to the migration guide to update your code accordingly and opt-in to FedCM to test your changes. Learn more: https://developers.google.com/identity/gsi/web/guides/fedcm-migration?s=dc#display_moment and https://developers.google.com/identity/gsi/web/guides/fedcm-migration?s=dc#skipped_moment @ https://accounts.google.com/gsi/client:80
|
||||||
|
[ 1937ms] [ERROR] Not signed in with the identity provider. @ https://chatgpt.com/:0
|
||||||
|
[ 1955ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "676035580" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 1963ms] [WARNING] WARN [Statsig] The user does not have the required id_type "userID" for Gate "3813654834" @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
|
[ 3454ms] [ERROR] [GSI_LOGGER]: FedCM get() rejects with NetworkError: Error retrieving a token. @ https://chatgpt.com/cdn/assets/2340486e-egjrxdhwh6ooomcg.js:0
|
||||||
9
.playwright-mcp/console-2026-03-17T15-38-39-958Z.log
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[ 1501ms] [ERROR] Loading the image 'https://match.adsrvr.org/track/cmf/google?g_uuid=&gdpr=0&gdpr_consent=&ttd_tdid=2872c9e6-dc9f-43a6-814a-8937e1cd99a1&google_gid=CAESEJK-ksPAjxfHKDagAqNFNxk&google_cver=1' violates the following Content Security Policy directive: "img-src 'self' blob: https://images.ctfassets.net:* https://www.google.com https://www.google-analytics.com https://sst.1passwordservices.com https://cm.g.doubleclick.net https://stats.g.doubleclick.net https://insight.adsrvr.org https://px.mountain.com https://b.6sc.co https://px.ads.linkedin.com/". The action has been blocked. @ https://1password.com/:0
|
||||||
|
[ 14341ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-mobile-light@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
|
[ 14341ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-desktop-dark@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
|
[ 14341ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-mobile-dark@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
|
[ 14341ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-desktop-light@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
|
[ 20363ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-mobile-light@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
|
[ 20363ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-desktop-dark@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
|
[ 20363ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-mobile-dark@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
|
[ 20363ms] [WARNING] The resource https://1password.com/logo-images/1password-logo-desktop-light@2x.png was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it has an appropriate `as` value and it is preloaded intentionally. @ https://1password.com/:0
|
||||||
7
.playwright-mcp/console-2026-03-17T15-39-05-477Z.log
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[ 1038ms] [WARNING] An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can escape its sandboxing. @ https://www.lastpass.com/assets/js/main.js?t=18112025120306:1
|
||||||
|
[ 1784ms] [ERROR] Loading the image 'https://www.google.ca/pagead/1p-user-list/732052380/?label=yvW5CKrAxOUBEJz3iN0C&guid=ON&script=0&ct_cookie_present=false&random=1275753962&crd=CLTesQIIobixAgixwbECCLDBsQIIscOxAgiKxbECCMLJsQIItMaxAgiT2rECCNvcsQIIh9uxAgjTxbECCOvMsQII7c6xAgjVz7ECCPTasQIIl9SxAgjJ27ECCLHhsQIIs-GxAgim3bECCLDesQIIgNuxAg&cerd=CgSH3b0t&fsk=ChAI8PfjzQYQ6eaqideo0sBLEiwAYa62m4eAEx1ccdnTx6aJzSX8it02TMCpJI_3oDPQtKxwQRiC3aIgH8kh2BoCh2Q&pscrd=IhMI0_PikaKnkwMVOu8oBR1-bws5MgwIA2IICAAQABgAIAAyDAgEYggIABAAGAAgADIMCAdiCAgAEAAYACAAMgwICGIICAAQABgAIAAyDAgJYggIABAAGAAgADIMCApiCAgAEAAYACAAMgwIAmIICAAQABgAIAAyDAgLYggIABAAGAAgADIMCBViCAgAEAAYACAAMgwIH2IICAAQABgAIAAyDAgTYggIABAAGAAgADIMCBJiCAgAEAAYACAAOhdodHRwczovL2Nkbi5scHV0aWwuY29tL3oMCAliCAgAEAAYACAA&is_vtc=1&cid=CAQSvgEABaugfSVNQtMbEZBzYftNVEoanGPwbzgM5Sq5HbzzdDAEF1z8atusXTxJZoPSGm9v0rkXBC6R8XJdIxLCD-nV_XORCPbuh0tqDIVX2sPU6aHiZMrGXFjryGEOchqZNdOyHilGdk0s1Ju2MM13eMo4tFcqrtvWCO3v9FMydUPv1MQhzkj5DF7j1EAZNPHifAhQhP6_aSWgMUi2Tsy18rCBgmW51LB-x50ilLNXLGOtSoyp-PEb6EbwLeAMzyJv&random=111720498&ipr=y' violates the following Content Security Policy directive: "img-src https://tvspix.com https://arttrk.com https://*.sitescout.com https://ct.capterra.com https://b.6sc.co http://b.6sc.co https://bat.bing.com http://bat.bing.com https://dpx.airpr.com http://dpx.airpr.com https://appwiki.nl http://appwiki.nl https://snap.licdn.com https://px4.ads.linkedin.com https://px.ads.linkedin.com http://px.ads.linkedin.com https://www.facebook.com https://adservice.google.com https://ad.doubleclick.net http://p.adsymptotic.com https://p.adsymptotic.com https://www.linkedin.com https://evt.undertone.com https://www.googleadservices.com https://adfarm.mediaplex.com https://bh.contextweb.com https://bm.adentifi.com https://sy.eu.angsrvr.com https://rtb.gumgum.com https://partners.tremorhub.com https://cookiemonster.coull.com https://sync.search.spotxchange.com https://vmg.host https://synch.optimatic.com https://www.google.com https://sync.outbrain.com https://pixel.advertising.com https://us-u.openx.net https://rs.gwallet.com https://secimg.vmmpxl.com https://secure.adnxs.com https://secure.leadback.advertising.com http://t.co https://t.co https://analytics.twitter.com https://secure.ace-tag.advertising.com http://googleads.g.doubleclick.net https://googleads.g.doubleclick.net https://ad.360yield.com https://insight.adsrvr.org https://tapestry.tapad.com https://ads.undertone.com https://tag.clrstm.com https://d.adroll.com https://cdn.lputil.com https://gce-sc.bidswitch.net https://x.bidswitch.net https://idsync.rlcdn.com https://stags.bluekai.com https://sync.outbrain.com https://trc.taboola.com https://bsw.digitru.st https://dsum-sec.casalemedia.com https://ib.adnxs.com https://ums.adtechus.com https://pixel.rubiconproject.com https://simage2.pubmatic.com https://us-u.openx.net https://io.narrative.io https://dpm.demdex.net https://beacon.krxd.net https://jp-u.openx.net https://ads.yahoo.com https://cm.g.doubleclick.net https://dc.ads.linkedin.com https://*.fls.doubleclick.net". The action has been blocked. @ https://cdn.lputil.com/lpassets/alero/index.v1.7.html:0
|
||||||
|
[ 1978ms] [ERROR] Loading the script 'https://snap.licdn.com/li.lms-analytics/insight.min.js' violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.6sc.co https://*.6sense.com https://*.cardinaltrusted.com/ https://*.datadoghq-browser-agent.com https://analytics.web.lastpass.dev https://www.goto.com/ https://*.cardinalcommerce.com https://cdn.lmiutil.com/ https://cdn.lputil.com https://*.go-mpulse.net https://lastpass.com/m.php/quickdl2 https://*.cybersource.com/ https://www.google-analytics.com https://www.googletagmanager.com https://www.google-analytics.com/ https://www.googletagmanager.com/ https://logmeincdn.azureedge.net https://cdn.clicktale.net/ https://*.lastpass.com https://lastpass.com https://www.youtube.com https://*.ytimg.com https://cdnssl.clicktale.net/ https://www.sc.pages04.net https://cdn.getsmartcontent.com/ https://api.bizographics.com https://us-east-1.profile-api.ads.linkedin.com https://s.getsmartcontent.com https://az416426.vo.msecnd.net https://connect.facebook.net". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback. The action has been blocked. @ https://www.googletagmanager.com/gtm.js?id=GTM-NK3NNBHW&l=dataLayer_standard:215
|
||||||
|
[ 1979ms] [ERROR] Loading the script 'https://tags.srv.stackadapt.com/events.js' violates the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.6sc.co https://*.6sense.com https://*.cardinaltrusted.com/ https://*.datadoghq-browser-agent.com https://analytics.web.lastpass.dev https://www.goto.com/ https://*.cardinalcommerce.com https://cdn.lmiutil.com/ https://cdn.lputil.com https://*.go-mpulse.net https://lastpass.com/m.php/quickdl2 https://*.cybersource.com/ https://www.google-analytics.com https://www.googletagmanager.com https://www.google-analytics.com/ https://www.googletagmanager.com/ https://logmeincdn.azureedge.net https://cdn.clicktale.net/ https://*.lastpass.com https://lastpass.com https://www.youtube.com https://*.ytimg.com https://cdnssl.clicktale.net/ https://www.sc.pages04.net https://cdn.getsmartcontent.com/ https://api.bizographics.com https://us-east-1.profile-api.ads.linkedin.com https://s.getsmartcontent.com https://az416426.vo.msecnd.net https://connect.facebook.net". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback. The action has been blocked. @ :0
|
||||||
|
[ 2110ms] [WARNING] Survey ga event not sent. Ga not on page @ https://www.lastpass.com/assets/js/main.js?t=18112025120306:1
|
||||||
|
[ 2282ms] [WARNING] [Meta Pixel] - Duplicate Pixel ID: 735319568855259. @ https://connect.facebook.net/en_US/fbevents.js:175
|
||||||
|
[ 2600ms] [ERROR] Loading the image 'https://www.google.ca/pagead/1p-user-list/943753783/?random=858027134&fst=1773759600000&cv=10&fmt=3&bg=ffffff&guid=ON&u_w=1920&u_h=1080>m=45j91e63d1z89219860845z99105696184za20gzb9219860845zd9219860845&url=https%3A%2F%2Fwww.lastpass.com%2F&tiba=%231%20Password%20Manager%20%26%20Vault%20App%20with%20Single-Sign%20On%20%26%20MFA%20Solutions%20-%20LastPass&auid=1538425297.1773761939&uaa=x86&uab=64&uafvl=Not%253AA-Brand%3B99.0.0.0%7CGoogle%2520Chrome%3B145.0.7632.160%7CChromium%3B145.0.7632.160&uamb=0&uam=&uap=Windows&uapv=19.0.0&uaw=0&dma=0&npa=0&gcd=13l3l3l3l1l1&pscdl=noapi&_is_sw=f15s0t3&tag_exp=103116026~103200004~115616986~115938466~115938469~116024733~116024736~117484252&_tu=BA&is_vtc=1&cid=CAQSvgEABaugff1ZQWiGnms_Bp21Q6uDHetYGAno162uKAHuyl1uk9tRQuR_8xSGvZGXZDIoxnw9-cvsvX_3q-e1AQBfzrKB-j-VOH84vWXBETHXJLv9dyg2hZjD6oYz_DwN1F19tNkg-UmhQltoX2l60QvVHuA5jE0kh3chICFYWUM0GcJ3RfoWj9iejaeUSAt2qotukNbMN02AveHP0frCSk-Tu1NXrOPLbIxVfHDOIFh2pXUKNBOCl-qFV244wOc9&random=2438417780&ipr=y' violates the following Content Security Policy directive: "img-src 'self' https://hostedseal.trustarc.com/ https://*.6sc.co https://www.facebook.com https://www.googletagmanager.com https://*.businesswire.com/ https://*.truste.com https://*.lmiutil.com/ https://*.lputil.com/ https://px.ads.linkedin.com/ https://sp1.convertro.com/api/hit/lastpass/ https://www.google-analytics.com/ https://*.company-target.com/ https://logmeincdn.azureedge.net/ https://*.company-target.com/* https://*.lastpass.com https://lastpass.com https://*.go-mpulse.net https://*.akstat.io/ data: https://*.g2.com https://*.akstat.io/ https://*.go-mpulse.net https://*.company-target.com/* https://*.adnxs.com/ https://*.doubleclick.net https://*.google-analytics.com https://www.google.com https://www.facebook.com/tr https://t.co/i/adsct https://analytics.twitter.com/i/adsct https://img.youtube.com https://adfarm.mediaplex.com/ad/bk/ https://*.rfihub.com/ https://secure.leadback.advertising.com https://secure.ace-tag.advertising.com https://iad-login.dotomi.com https://*.company-target.com/ https://www07.clicktale.net/ https://d.adroll.com https://secimg.vmmpxl.com https://rs.gwallet.com https://s-cs.send.microad.jp https://pixel.rubiconproject.com https://*.openx.net https://dpm.demdex.net https://ads.yahoo.com https://dsum-sec.casalemedia.com https://simage2.pubmatic.com https://trc.taboola.com https://x.bidswitch.net https://idsync.rlcdn.com https://stags.bluekai.com https://ums.adtechus.com https://io.narrative.io https://atemda.com https://t.brand-server.com https://pixel.quantserve.com". The action has been blocked. @ https://www.lastpass.com/:0
|
||||||
1
.playwright-mcp/console-2026-03-18T20-08-48-067Z.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[ 816ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:8765/favicon.ico:0
|
||||||
1
.playwright-mcp/console-2026-03-18T20-27-17-867Z.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[ 442ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://localhost:8766/favicon.ico:0
|
||||||
1
.playwright-mcp/console-2026-03-18T20-43-11-414Z.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[ 254ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8765/favicon.ico:0
|
||||||
1
.playwright-mcp/console-2026-03-18T20-53-05-347Z.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[ 433ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8765/favicon.ico:0
|
||||||
1
.playwright-mcp/console-2026-03-18T21-06-29-639Z.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[ 557ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8766/favicon.ico:0
|
||||||
203
CLAUDE-beta.md
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
# CLAUDE.md — SVS MSP Calculator
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What This Is
|
||||||
|
|
||||||
|
**SVS MSP Calculator** — a live pricing and quote tool used by the sales team on screen during prospect calls.
|
||||||
|
HTML5 / CSS3 / JS. No frameworks, no build tools. Open `SVS-MSP-Calculator.html` in a browser — it runs.
|
||||||
|
|
||||||
|
**Status:** Active development — current focus is GUI / UI improvement.
|
||||||
|
Quote logic and pricing engine are stable. Do not touch without explicit approval.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### The Update Loop
|
||||||
|
|
||||||
|
Every input change triggers this pipeline:
|
||||||
|
|
||||||
|
```
|
||||||
|
Input event → readFormState() → calculateQuote(state, pricing) → renderQuote(quote) → mobileSync()
|
||||||
|
```
|
||||||
|
|
||||||
|
`SVS-MSP-Calculator.js` orchestrates this via `update()`. The engine is a pure function — state in, quote out. Render writes to DOM. Mobile sync clones to the mobile panel. **Any change that touches this chain affects the entire app.**
|
||||||
|
|
||||||
|
### CSS Cascade
|
||||||
|
|
||||||
|
9 modular CSS files loaded via `SVS-MSP-Calculator.css` (master import):
|
||||||
|
|
||||||
|
```
|
||||||
|
tokens.css → base.css → layout.css → components.css → responsive.css
|
||||||
|
↕
|
||||||
|
light.css / glass.css (theme overrides)
|
||||||
|
↕
|
||||||
|
print.css
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tokens are the source of truth.** 60+ semantic variables (`--ink`, `--paper`, `--accent`, `--surface-*`, `--print-*`). Changing a token ripples through all 3 themes, all components, and print output.
|
||||||
|
|
||||||
|
Themes are pure CSS variable overrides on `:root` — no HTML changes, no JS logic. `theme-manager.js` toggles a class; the cascade does the rest.
|
||||||
|
|
||||||
|
### Mobile Sync
|
||||||
|
|
||||||
|
`mobile-sync.js` physically clones the sidebar DOM into a bottom-sheet panel (`#mobileQuotePanel`) and appends `_m` to every cloned ID. It then syncs content, classes, styles, and checkbox states from desktop → mobile after each `update()`.
|
||||||
|
|
||||||
|
**If you add a new sidebar element, mobile-sync must know about it or mobile breaks silently.**
|
||||||
|
|
||||||
|
### The Sidebar Is the Product
|
||||||
|
|
||||||
|
The right column (`.sidebar`) is what the prospect stares at during the call. Hero numbers: MRR, per-user cost, annual total. Every UI change should be evaluated from: **does this make the sidebar clearer?**
|
||||||
|
|
||||||
|
### Print / PDF
|
||||||
|
|
||||||
|
`SVS-MSP-Calculator-print.css` is a parallel rendering target with its own token set (`--print-*`). At `@media print` it hides all interactive controls, forces sections open, and outputs a clean A4 document. Print is not an afterthought — it's a sales deliverable.
|
||||||
|
|
||||||
|
### localStorage
|
||||||
|
|
||||||
|
3 keys: quote state (full form data as JSON), quote reference ID, theme preference. Save/load via `quote-persistence.js`. `Ctrl+S` / `Ctrl+L` shortcuts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
Things that must be true before and after every session. Each has a verification step.
|
||||||
|
|
||||||
|
| Invariant | Verify |
|
||||||
|
|-----------|--------|
|
||||||
|
| All unit tests pass | `node tests/test-quote-engine.js` — 254/254 |
|
||||||
|
| All 3 themes render correctly | Playwright-check Dark, Light, Glass after any CSS change |
|
||||||
|
| DOM IDs unchanged | Never rename — `mobile-sync.js` maps 100+ desktop↔mobile pairs silently |
|
||||||
|
| localStorage round-trip | Save → reload → confirm all values restore |
|
||||||
|
| Print output clean | Verify after any CSS change — print has its own cascade |
|
||||||
|
| Mobile panel matches sidebar | Check mobile bottom-sheet after any sidebar change |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Working Rules
|
||||||
|
|
||||||
|
How Claude behaves while working. Not testable — behavioral.
|
||||||
|
|
||||||
|
1. **Read before editing** — always inspect current code first
|
||||||
|
2. **Quote engine is locked** — no changes to `quote-engine.js`, `quote-pricing.js`, or `package-prices-data.js` without explicit approval
|
||||||
|
3. **No frameworks, no build tools** — vanilla JS by design
|
||||||
|
4. **No broad rewrites** — surgical, approved changes only
|
||||||
|
5. **Ask before assuming** — when requirements are unclear, ask
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Do Not Touch
|
||||||
|
|
||||||
|
| Thing | Why |
|
||||||
|
|-------|-----|
|
||||||
|
| `fontawesomekit/` | Huge vendor directory. Reference known FA icons by name when needed — never scan this directory |
|
||||||
|
| `pre-alpha/` | Legacy/experimental files. Ignore unless explicitly asked |
|
||||||
|
| Quote engine files | `quote-engine.js`, `quote-pricing.js`, `package-prices-data.js` — stable, tested, locked |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
1. **Sales clarity over visual novelty** — prospects read numbers at a glance
|
||||||
|
2. **The sidebar is the hero** — treat it like a financial summary
|
||||||
|
3. **Dark theme is flagship** — Light and Glass must meet the same bar
|
||||||
|
4. **Copy is UX** — every label and nudge is a design decision
|
||||||
|
5. **Mobile is first-class** — a sales rep on a tablet must run a full quote
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### HTML Structure
|
||||||
|
|
||||||
|
6 collapsible sections in the main column, sticky sidebar in the right column:
|
||||||
|
|
||||||
|
| Section | ID | Content |
|
||||||
|
|---------|----|---------|
|
||||||
|
| I — Site Management | `#sec-01` | Admin fee, floor $650 |
|
||||||
|
| II — User Package | `#sec-02` | Per-user pricing, M365/BYOL |
|
||||||
|
| III — Endpoint Package | `#sec-03` | Device count |
|
||||||
|
| IV — Server Management | `#sec-04` | Server count |
|
||||||
|
| V — Zero Trust HaaS | `#sec-05` | Seats, routers |
|
||||||
|
| VI — VoIP UCaaS | `#sec-06` | Seats, tiers |
|
||||||
|
|
||||||
|
**Layout:** CSS Grid — `3fr | 2fr` (main | sidebar). Collapses to single column at 1100px. Mobile pill + bottom-sheet panel below 1100px.
|
||||||
|
|
||||||
|
**Z-index stack:** 400 (modals) → 300 (mobile panel) → 200 (mobile pill) → 100 (top bar) → 10 (sidebar)
|
||||||
|
|
||||||
|
### JS Files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `SVS-MSP-Calculator.js` | Orchestrator — `update()` loop, form reading, event wiring |
|
||||||
|
| `quote-engine.js` | Pure calculation — `calculateQuote(state, pricing)` → quote object |
|
||||||
|
| `quote-pricing.js` | Pricing defaults (32 rates/fees) — `SVSQuotePricing.getSnapshot()` |
|
||||||
|
| `package-prices-data.js` | External pricing data (optional override) |
|
||||||
|
| `quote-render.js` | Writes calculated quote to DOM elements |
|
||||||
|
| `quote-persistence.js` | localStorage save/load + `Ctrl+S`/`Ctrl+L` |
|
||||||
|
| `quote-export.js` | Printable/exportable quote HTML generation |
|
||||||
|
| `quote-import.js` | Load saved quotes from JSON |
|
||||||
|
| `mobile-sync.js` | Clones sidebar → mobile panel, syncs on every `update()` |
|
||||||
|
| `theme-manager.js` | Dark/Light/Glass toggle, persists preference |
|
||||||
|
|
||||||
|
### CSS Files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `SVS-MSP-Calculator.css` | Master import — loads all modules |
|
||||||
|
| `*-tokens.css` | Design tokens — 60+ semantic variables, source of truth |
|
||||||
|
| `*-base.css` | Body, top bar, theme toggle |
|
||||||
|
| `*-layout.css` | Grid, page layout, sidebar, modals |
|
||||||
|
| `*-components.css` | Section cards, buttons, icons, form controls |
|
||||||
|
| `*-responsive.css` | Media queries — 1100px and 600px breakpoints |
|
||||||
|
| `*-light.css` | Light theme — `:root` variable overrides only |
|
||||||
|
| `*-glass.css` | Glass theme — gradients, blur, `color-scheme: dark` |
|
||||||
|
| `*-print.css` | Print/PDF — own token set, aggressive cleanup, A4-ready |
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
254 tests across 47 groups. Custom minimal harness (no framework). Covers:
|
||||||
|
pricing, discounts, add-ons, VoIP tiers, HST, contract terms, edge cases, admin fees.
|
||||||
|
|
||||||
|
Run: `node tests/test-quote-engine.js`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools & When to Use Them
|
||||||
|
|
||||||
|
| Tool | Use when |
|
||||||
|
|------|----------|
|
||||||
|
| **Playwright** | Visual verification — viewing HTML/CSS across themes |
|
||||||
|
| **ui-ux-pro-max** | Any design decision — invoke before touching CSS |
|
||||||
|
| **superpowers** | Planning, parallel agents, debugging, TDD, branch/commit |
|
||||||
|
| **frontend-design** | Building or modifying UI components |
|
||||||
|
| **code-review** | Before marking any task complete |
|
||||||
|
| **code-simplifier** | After implementation — clean up without changing behavior |
|
||||||
|
| **context-mode** | Large output (>20 lines) — routes through sandbox to protect context window. Use `ctx_batch_execute` for multi-command research, `ctx_search` for follow-up queries, `ctx_execute`/`ctx_execute_file` for data processing, `ctx_fetch_and_index` for URL fetching |
|
||||||
|
| **accesslint** | Accessibility and colour contrast checking. Use `contrast-checker` for WCAG ratio checks on hex pairs, `use-of-color` to flag colour-only indicators, `reviewer` for full component audits. Minimum standard: WCAG 2.1 AA (4.5:1 normal text, 3:1 large text). Suggest replacement hex values on failure. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Start
|
||||||
|
|
||||||
|
1. Read `docs/SESSION-HANDOFF.md` — current state of the project
|
||||||
|
2. Run `node tests/test-quote-engine.js` — confirm 254/254
|
||||||
|
3. Ask the user what they want — do not assume, do not start changing things
|
||||||
|
|
||||||
|
Read other docs only when the task requires them:
|
||||||
|
- `docs/QUICK-REF.md` — file map, DOM IDs, pricing constants
|
||||||
|
- `docs/quote-rules.md` — pricing / business logic
|
||||||
|
- `docs/DECISION-LOG.md` — has this decision already been made?
|
||||||
|
- `docs/KNOWN-ISSUES.md` — is this bug already tracked?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session End
|
||||||
|
|
||||||
|
1. Run `node tests/test-quote-engine.js` — all 254 must pass
|
||||||
|
2. Update `docs/SESSION-HANDOFF.md` as a **current state snapshot**:
|
||||||
|
- What is the state of the project right now (not a history log)
|
||||||
|
- What files are in what state
|
||||||
|
- What is next
|
||||||
|
3. Commit if approved by user — one concern per commit
|
||||||
59
CLAUDE-nextsteps.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Next Steps — SVS MSP Calculator
|
||||||
|
|
||||||
|
**Updated:** 2026-03-18
|
||||||
|
**Purpose:** Resume guide for next session.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Just Completed (2026-03-18, committed as 4c53af9)
|
||||||
|
|
||||||
|
Sidebar UX polish pass:
|
||||||
|
- Unified dividers → one `--sidebar-rule` token, dashed everywhere
|
||||||
|
- Letter-spacing → `--text-spacing-money: 0.05em` token
|
||||||
|
- Font floor → 11px minimum (6 locations bumped)
|
||||||
|
- Contrast → muted colors darkened to 6.5:1+ on all themes
|
||||||
|
- Opacity → removed from 9 double-muted text elements
|
||||||
|
- Total line border → scoped to monthly breakdown only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Session: Google Fonts + GUI Polish
|
||||||
|
|
||||||
|
### Step 1: Google Fonts Selection
|
||||||
|
- **Approved:** up to 3 fonts (preferred), 4 hard cap
|
||||||
|
- **Currently loaded:** Poppins (hero numbers), DM Mono (monospace values)
|
||||||
|
- **Need:** A clean sans-serif for body text, labels, and UI copy
|
||||||
|
- **Candidates to evaluate:** Inter, IBM Plex Sans, Source Sans 3, Outfit, or similar
|
||||||
|
- **Approach:** Pick font → load from Google Fonts CDN → apply to body/labels → verify all 3 themes
|
||||||
|
|
||||||
|
### Step 2: GUI Polish List A
|
||||||
|
- Contract Term pills — more vertical padding on unselected
|
||||||
|
- Section card collapsed headers — slightly tight vertically
|
||||||
|
- "MANAGED IT SERVICES (SECTIONS I, II, III)" label — low contrast
|
||||||
|
- Insight carousel text — more line-height
|
||||||
|
- "QUOTE NOTES" label spacing — inconsistent with other section labels
|
||||||
|
|
||||||
|
### Step 3: Typography Token Pass
|
||||||
|
- Tokenize remaining hardcoded letter-spacing (~20 locations)
|
||||||
|
- Tokenize hardcoded font-sizes
|
||||||
|
- Move sidebar typography tokens from components.css → tokens.css
|
||||||
|
- Phase 4: icon/button sizing tokens
|
||||||
|
- Phase 6: responsive breakpoint font-sizes (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools Available — USE THEM
|
||||||
|
|
||||||
|
| Tool | When |
|
||||||
|
|------|------|
|
||||||
|
| **Playwright MCP** | Visual verification across Dark/Light/Glass |
|
||||||
|
| **accesslint** | Contrast checking on any color changes |
|
||||||
|
| **superpowers** | Planning, parallel agents, TDD, debugging |
|
||||||
|
| **context-mode** | Large output processing (>20 lines) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resume Prompt
|
||||||
|
|
||||||
|
Tell Claude:
|
||||||
|
> "Read docs/SESSION-HANDOFF.md. Pick up at Google Fonts selection, then GUI polish list A."
|
||||||
359
CLAUDE.md
@@ -1,248 +1,203 @@
|
|||||||
# CLAUDE.md — SVS MSP Calculator
|
# CLAUDE.md — SVS MSP Calculator
|
||||||
|
|
||||||
> Master instruction set for Claude Code. This is the SINGLE file to read on session start.
|
---
|
||||||
> It references deeper docs — do not duplicate them here.
|
|
||||||
|
|
||||||
## Project Identity
|
## What This Is
|
||||||
|
|
||||||
**App:** SVS MSP Calculator — a live quote/pricing calculator for SVS Managed Services.
|
**SVS MSP Calculator** — a live pricing and quote tool used by the sales team on screen during prospect calls.
|
||||||
**Used by:** Sales team, live on screen with prospects during discovery calls.
|
HTML5 / CSS3 / JS. No frameworks, no build tools. Open `SVS-MSP-Calculator.html` in a browser — it runs.
|
||||||
**Tech:** Vanilla HTML5 / CSS3 / JS (ES5-compatible). No frameworks, no npm, no build tools. Open `SVS-MSP-Calculator.html` in a browser — it runs.
|
|
||||||
**Status:** Alpha-ready, pushing to Beta.
|
|
||||||
**Tests:** 254 passing (`node tests/test-quote-engine.js`)
|
|
||||||
**Themes:** 3 — Dark (flagship), Light, Glass
|
|
||||||
|
|
||||||
For full architecture, file maps, DOM IDs, and pricing constants see `docs/QUICK-REF.md`.
|
**Status:** Active development — current focus is GUI / UI improvement.
|
||||||
For business logic and pricing rules see `docs/quote-rules.md`.
|
Quote logic and pricing engine are stable. Do not touch without explicit approval.
|
||||||
For the complete architecture brief see `docs/MASTER-SESSION-PROMPT.md`.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Session Start Protocol
|
## How It Works
|
||||||
|
|
||||||
On every new conversation, execute in order:
|
### The Update Loop
|
||||||
|
|
||||||
1. **Read context** (parallel):
|
Every input change triggers this pipeline:
|
||||||
- `docs/SESSION-HANDOFF.md` — what happened last, what is next
|
|
||||||
- `docs/QUICK-REF.md` — file map, DOM IDs, pricing, danger zones
|
|
||||||
2. **Run baseline tests:** `node tests/test-quote-engine.js` — confirm 254/254 pass
|
|
||||||
3. **Ask the user** what they want to work on. Do not assume. Do not start changing things.
|
|
||||||
4. **Read task-specific docs only when needed:**
|
|
||||||
- `docs/quote-rules.md` — pricing or business logic work
|
|
||||||
- `docs/regression-checklist.md` — validation work
|
|
||||||
- `docs/MASTER-SESSION-PROMPT.md` — unfamiliar areas, full architecture
|
|
||||||
- `docs/DECISION-LOG.md` — check if a relevant decision was already made
|
|
||||||
- `docs/KNOWN-ISSUES.md` — check if the issue is already tracked
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Session End Protocol
|
|
||||||
|
|
||||||
Before ending any session:
|
|
||||||
|
|
||||||
1. **Run full test suite:** `node tests/test-quote-engine.js` — all 254 must pass
|
|
||||||
2. **Update `docs/SESSION-HANDOFF.md`** with:
|
|
||||||
- What was done (brief, specific)
|
|
||||||
- Files modified (table format)
|
|
||||||
- Test status (pass count)
|
|
||||||
- What is next (prioritized)
|
|
||||||
3. **Update `docs/CHECKPOINT.md`** if structural work was completed
|
|
||||||
4. **Update `docs/DECISION-LOG.md`** if any decisions were made
|
|
||||||
5. **Use `superpowers:finishing-a-development-branch`** for commit and cleanup
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Orchestration Engine — Superpowers
|
|
||||||
|
|
||||||
Use the **superpowers** plugin as the execution backbone for all non-trivial work.
|
|
||||||
|
|
||||||
### Workflow Selection
|
|
||||||
|
|
||||||
| Situation | Superpowers Skill to Use |
|
|
||||||
|-----------|--------------------------|
|
|
||||||
| Planning any feature or multi-step work | `superpowers:writing-plans` |
|
|
||||||
| Executing a plan with review gates | `superpowers:subagent-driven-development` |
|
|
||||||
| 2+ independent tasks (e.g., fix CSS in all 3 themes) | `superpowers:dispatching-parallel-agents` |
|
|
||||||
| Investigating a bug or failure | `superpowers:systematic-debugging` |
|
|
||||||
| Writing or modifying quote engine logic | `superpowers:test-driven-development` |
|
|
||||||
| Final checks before marking work done | `superpowers:verification-before-completion` |
|
|
||||||
| Committing, branch cleanup, merge prep | `superpowers:finishing-a-development-branch` |
|
|
||||||
| Visual brainstorming for design or UX | `superpowers:brainstorming` |
|
|
||||||
|
|
||||||
### Subagent-Driven Development Flow
|
|
||||||
|
|
||||||
For any plan with 2+ tasks, use `superpowers:subagent-driven-development`:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Per task:
|
Input event → readFormState() → calculateQuote(state, pricing) → renderQuote(quote) → mobileSync()
|
||||||
1. Dispatch implementer subagent (with role-specific model — see below)
|
|
||||||
2. Spec compliance review (did it match requirements?)
|
|
||||||
3. Code quality review (is it well-built?)
|
|
||||||
4. Mark task complete
|
|
||||||
After all tasks:
|
|
||||||
Final code review → superpowers:finishing-a-development-branch
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
`SVS-MSP-Calculator.js` orchestrates this via `update()`. The engine is a pure function — state in, quote out. Render writes to DOM. Mobile sync clones to the mobile panel. **Any change that touches this chain affects the entire app.**
|
||||||
|
|
||||||
## Agent Roles & Model Routing
|
### CSS Cascade
|
||||||
|
|
||||||
When dispatching subagents (via superpowers or directly), route to the right role and model.
|
9 modular CSS files loaded via `SVS-MSP-Calculator.css` (master import):
|
||||||
|
|
||||||
### Frontend Coder — Opus (default)
|
|
||||||
**When:** JS logic, CSS changes, HTML structure, bug fixes, refactoring.
|
|
||||||
**How:** Surgical precision. Read the file first, make the minimal change.
|
|
||||||
**Post-change:** Run the full validation pipeline.
|
|
||||||
|
|
||||||
### UI/UX Designer — Opus + ui-ux-pro-max Skill
|
|
||||||
**When:** Design decisions — palettes, typography, spacing, layout, visual hierarchy, component styling.
|
|
||||||
**How:** Invoke the ui-ux-pro-max skill BEFORE implementation. Feed design system output to the implementer subagent.
|
|
||||||
**Sub-skills:** banner-design, brand, design-system, design, slides, ui-styling
|
|
||||||
**Constraint:** Translate all output to vanilla CSS — this project has no framework.
|
|
||||||
|
|
||||||
### Copywriter — Sonnet Model
|
|
||||||
**When:** ALL user-facing text — button labels, section descriptions, nudge messages, tooltip copy, sidebar labels, sales language, feature descriptions, comparison text, pitch bar copy.
|
|
||||||
**How:** Dispatch Agent with `model: "sonnet"`. Provide context about:
|
|
||||||
- The sales use case (live on calls with prospects)
|
|
||||||
- The specific UI element being written for
|
|
||||||
- The current text (if revising)
|
|
||||||
**Tone:** Confident, concise, client-facing. Not marketing fluff. This is a tool used live.
|
|
||||||
**Rule:** Copy is UX. Every label guides behavior. Every nudge drives a decision.
|
|
||||||
|
|
||||||
### Calculation Validator — Opus
|
|
||||||
**When:** After ANY change to `quote-engine.js`, `quote-pricing.js`, `package-prices-data.js`, or sidebar render values.
|
|
||||||
**How:**
|
|
||||||
1. Run `node tests/test-quote-engine.js` — all 254 must pass
|
|
||||||
2. Manually verify 2+ quote configs against the verification matrix in `docs/MASTER-SESSION-PROMPT.md` (Priority 4)
|
|
||||||
3. Cross-check sidebar display values against engine output
|
|
||||||
4. Verify admin fee floor/threshold logic at edge cases (0 users, 1 endpoint)
|
|
||||||
|
|
||||||
### QA / Regression Tester — Opus
|
|
||||||
**When:** After any visual, structural, or behavioral change.
|
|
||||||
**How:** Run the validation pipeline below. Use Playwright MCP for visual verification.
|
|
||||||
**Reference:** `docs/regression-checklist.md` for full manual QA procedures.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Validation Pipeline
|
|
||||||
|
|
||||||
After every change, validate in order. Stop and fix at the first failure.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
1. TESTS node tests/test-quote-engine.js (254/254 must pass)
|
tokens.css → base.css → layout.css → components.css → responsive.css
|
||||||
2. SYNTAX No console errors on fresh browser load
|
↕
|
||||||
3. THEMES Playwright: verify Dark, Light, Glass all render correctly
|
light.css / glass.css (theme overrides)
|
||||||
4. MOBILE Playwright: verify at 375px — floating MRR pill, bottom sheet, sync
|
↕
|
||||||
5. PRINT If CSS touched: verify print output is unaffected
|
print.css
|
||||||
6. PERSISTENCE If state/form touched: save → reload → verify all values restore
|
|
||||||
7. EXPORT If export touched: JSON export valid, version field present
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
**Tokens are the source of truth.** 60+ semantic variables (`--ink`, `--paper`, `--accent`, `--surface-*`, `--print-*`). Changing a token ripples through all 3 themes, all components, and print output.
|
||||||
|
|
||||||
## Hard Constraints
|
Themes are pure CSS variable overrides on `:root` — no HTML changes, no JS logic. `theme-manager.js` toggles a class; the cascade does the rest.
|
||||||
|
|
||||||
These are inviolable. Every change must preserve them.
|
### Mobile Sync
|
||||||
|
|
||||||
| # | Constraint |
|
`mobile-sync.js` physically clones the sidebar DOM into a bottom-sheet panel (`#mobileQuotePanel`) and appends `_m` to every cloned ID. It then syncs content, classes, styles, and checkbox states from desktop → mobile after each `update()`.
|
||||||
|---|-----------|
|
|
||||||
| 1 | **DOM IDs are a contract.** `mobile-sync.js` maps 100+ ID pairs (desktop ↔ `_m` suffix). Renaming breaks sync silently. |
|
**If you add a new sidebar element, mobile-sync must know about it or mobile breaks silently.**
|
||||||
| 2 | **Quote math is sacred.** Any `quote-engine.js` or `quote-pricing.js` change requires test validation. |
|
|
||||||
| 3 | **localStorage round-trip must work.** Key: `svs-msp-quote-v1`. Verify after form/state changes. |
|
### The Sidebar Is the Product
|
||||||
| 4 | **All 3 themes must work.** Dark (flagship), Light, Glass. Token/component changes cascade to all. |
|
|
||||||
| 5 | **Mobile parity maintained.** Sidebar clone in mobile panel must stay in sync. Usable at 375px. |
|
The right column (`.sidebar`) is what the prospect stares at during the call. Hero numbers: MRR, per-user cost, annual total. Every UI change should be evaluated from: **does this make the sidebar clearer?**
|
||||||
| 6 | **Print/PDF tested after CSS changes.** Print CSS is sensitive to component class changes. |
|
|
||||||
| 7 | **No framework or build-tool migration.** Vanilla JS by design. |
|
### Print / PDF
|
||||||
| 8 | **No broad rewrites.** Surgical, approved changes only. |
|
|
||||||
| 9 | **Read before editing.** Always inspect current code before making changes. |
|
`SVS-MSP-Calculator-print.css` is a parallel rendering target with its own token set (`--print-*`). At `@media print` it hides all interactive controls, forces sections open, and outputs a clean A4 document. Print is not an afterthought — it's a sales deliverable.
|
||||||
| 10 | **Ask before assuming.** When requirements are ambiguous, ask the user. |
|
|
||||||
|
### localStorage
|
||||||
|
|
||||||
|
3 keys: quote state (full form data as JSON), quote reference ID, theme preference. Save/load via `quote-persistence.js`. `Ctrl+S` / `Ctrl+L` shortcuts.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Regression Hotspots
|
## Invariants
|
||||||
|
|
||||||
| Area | Risk | Why |
|
Things that must be true before and after every session. Each has a verification step.
|
||||||
|------|------|-----|
|
|
||||||
| `quote-engine.js` math | Critical | Wrong quotes in real sales calls |
|
| Invariant | Verify |
|
||||||
| localStorage round-trip | High | Silent failures lose configured quotes |
|
|-----------|--------|
|
||||||
| Mobile sync ID map | High | 100+ pairs desync silently if IDs change |
|
| All unit tests pass | `node tests/test-quote-engine.js` — 254/254 |
|
||||||
| Print/PDF CSS | Medium | Separate cascade, sensitive to class changes |
|
| All 3 themes render correctly | Playwright-check Dark, Light, Glass after any CSS change |
|
||||||
| Theme switching | Medium | All 3 themes affected by token changes |
|
| DOM IDs unchanged | Never rename — `mobile-sync.js` maps 100+ desktop↔mobile pairs silently |
|
||||||
| `update()` call chain | Medium | Side effects cascade: calc → render → sidebar → nudges → summaries → save |
|
| localStorage round-trip | Save → reload → confirm all values restore |
|
||||||
|
| Print output clean | Verify after any CSS change — print has its own cascade |
|
||||||
|
| Mobile panel matches sidebar | Check mobile bottom-sheet after any sidebar change |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Installed Plugins & Skills
|
## Working Rules
|
||||||
|
|
||||||
### Plugins (use when applicable)
|
How Claude behaves while working. Not testable — behavioral.
|
||||||
|
|
||||||
| Plugin | When to Use |
|
1. **Read before editing** — always inspect current code first
|
||||||
|--------|-------------|
|
2. **Quote engine is locked** — no changes to `quote-engine.js`, `quote-pricing.js`, or `package-prices-data.js` without explicit approval
|
||||||
| `superpowers` | Orchestration: planning, agents, reviews, TDD, debugging, branch mgmt |
|
3. **No frameworks, no build tools** — vanilla JS by design
|
||||||
| `frontend-design` | Frontend design patterns and implementation |
|
4. **No broad rewrites** — surgical, approved changes only
|
||||||
| `code-review` | Structured code review |
|
5. **Ask before assuming** — when requirements are unclear, ask
|
||||||
| `code-simplifier` | Simplification and cleanup passes |
|
|
||||||
| `playwright` | Browser automation, visual verification, screenshot comparison |
|
|
||||||
| `claude-md-management` | Maintaining this CLAUDE.md file |
|
|
||||||
| `skill-creator` | Creating new custom skills for this project |
|
|
||||||
|
|
||||||
### Skills (ui-ux-pro-max)
|
|
||||||
|
|
||||||
| Skill | When to Use |
|
|
||||||
|-------|-------------|
|
|
||||||
| `ui-ux-pro-max` | Main design intelligence — styles, colors, typography, UX rules |
|
|
||||||
| `design-system` | Token architecture, component specs |
|
|
||||||
| `brand` | Voice, visual identity, messaging consistency |
|
|
||||||
| `ui-styling` | Component styling (translate to vanilla CSS) |
|
|
||||||
| `design` | Logo, icons, visual assets |
|
|
||||||
| `banner-design` | Marketing banners and heroes |
|
|
||||||
| `slides` | HTML presentations |
|
|
||||||
|
|
||||||
**Search command** (requires Python 3):
|
|
||||||
```bash
|
|
||||||
python3 .claude/skills/ui-ux-pro-max/src/ui-ux-pro-max/scripts/search.py "<query>" --domain <domain>
|
|
||||||
```
|
|
||||||
Domains: `style`, `color`, `typography`, `product`, `landing`, `chart`, `ux`
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Commit Protocol
|
## Do Not Touch
|
||||||
|
|
||||||
- One concern per commit. Do not bundle unrelated changes.
|
| Thing | Why |
|
||||||
- Message format: `<area>: <what> — <why>`
|
|-------|-----|
|
||||||
- Examples: `css: fix glass theme sidebar blur — backdrop-filter not applying at 900px`
|
| `fontawesomekit/` | Huge vendor directory. Reference known FA icons by name when needed — never scan this directory |
|
||||||
- Examples: `engine: correct admin fee at zero users — floor logic was bypassed`
|
| `pre-alpha/` | Legacy/experimental files. Ignore unless explicitly asked |
|
||||||
- Do not commit temp files or `.bak-focusmode` files.
|
| Quote engine files | `quote-engine.js`, `quote-pricing.js`, `package-prices-data.js` — stable, tested, locked |
|
||||||
- Run validation pipeline before committing.
|
|
||||||
- Use `superpowers:finishing-a-development-branch` for final commit + cleanup.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Design Principles
|
## Design Principles
|
||||||
|
|
||||||
1. **Sales clarity over visual novelty.** Prospects must read numbers at a glance.
|
1. **Sales clarity over visual novelty** — prospects read numbers at a glance
|
||||||
2. **Trust through polish.** Misaligned inputs erode prospect confidence.
|
2. **The sidebar is the hero** — treat it like a financial summary
|
||||||
3. **Progressive disclosure.** Lead with MRR total. Detail unfolds on demand.
|
3. **Dark theme is flagship** — Light and Glass must meet the same bar
|
||||||
4. **Feedback immediacy.** Every input change updates sidebar within 1 frame.
|
4. **Copy is UX** — every label and nudge is a design decision
|
||||||
5. **Dark theme is the flagship.** Light and Glass must meet the same bar.
|
5. **Mobile is first-class** — a sales rep on a tablet must run a full quote
|
||||||
6. **The sidebar is the hero.** Design it like a financial summary, not a DOM dump.
|
|
||||||
7. **Copy is UX.** Labels, nudges, and button text are design decisions.
|
|
||||||
8. **Mobile is first-class.** A sales rep on a tablet must run a full quote.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## File Reference
|
## Architecture
|
||||||
|
|
||||||
See `docs/QUICK-REF.md` for the complete file map, DOM IDs, and pricing constants.
|
### HTML Structure
|
||||||
|
|
||||||
| Category | Key Files |
|
6 collapsible sections in the main column, sticky sidebar in the right column:
|
||||||
|----------|-----------|
|
|
||||||
| Orchestration | `SVS-MSP-Calculator.js`, `SVS-MSP-Calculator.html` |
|
| Section | ID | Content |
|
||||||
| Quote Engine | `quote-engine.js`, `quote-pricing.js`, `package-prices-data.js` |
|
|---------|----|---------|
|
||||||
| Rendering | `quote-render.js`, `mobile-sync.js`, `theme-manager.js` |
|
| I — Site Management | `#sec-01` | Admin fee, floor $650 |
|
||||||
| Persistence | `quote-persistence.js`, `quote-export.js`, `quote-import.js` |
|
| II — User Package | `#sec-02` | Per-user pricing, M365/BYOL |
|
||||||
| CSS Tokens | `SVS-MSP-Calculator-tokens.css` (source of truth for design tokens) |
|
| III — Endpoint Package | `#sec-03` | Device count |
|
||||||
| CSS Themes | `*-light.css`, `*-glass.css` |
|
| IV — Server Management | `#sec-04` | Server count |
|
||||||
| CSS Layout | `*-layout.css`, `*-components.css`, `*-responsive.css`, `*-print.css` |
|
| V — Zero Trust HaaS | `#sec-05` | Seats, routers |
|
||||||
| Tests | `tests/test-quote-engine.js` (254 tests, zero deps) |
|
| VI — VoIP UCaaS | `#sec-06` | Seats, tiers |
|
||||||
| Docs | `docs/QUICK-REF.md`, `docs/SESSION-HANDOFF.md`, `docs/MASTER-SESSION-PROMPT.md`, `docs/quote-rules.md`, `docs/DECISION-LOG.md`, `docs/KNOWN-ISSUES.md` |
|
|
||||||
|
**Layout:** CSS Grid — `3fr | 2fr` (main | sidebar). Collapses to single column at 1100px. Mobile pill + bottom-sheet panel below 1100px.
|
||||||
|
|
||||||
|
**Z-index stack:** 400 (modals) → 300 (mobile panel) → 200 (mobile pill) → 100 (top bar) → 10 (sidebar)
|
||||||
|
|
||||||
|
### JS Files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `SVS-MSP-Calculator.js` | Orchestrator — `update()` loop, form reading, event wiring |
|
||||||
|
| `quote-engine.js` | Pure calculation — `calculateQuote(state, pricing)` → quote object |
|
||||||
|
| `quote-pricing.js` | Pricing defaults (32 rates/fees) — `SVSQuotePricing.getSnapshot()` |
|
||||||
|
| `package-prices-data.js` | External pricing data (optional override) |
|
||||||
|
| `quote-render.js` | Writes calculated quote to DOM elements |
|
||||||
|
| `quote-persistence.js` | localStorage save/load + `Ctrl+S`/`Ctrl+L` |
|
||||||
|
| `quote-export.js` | Printable/exportable quote HTML generation |
|
||||||
|
| `quote-import.js` | Load saved quotes from JSON |
|
||||||
|
| `mobile-sync.js` | Clones sidebar → mobile panel, syncs on every `update()` |
|
||||||
|
| `theme-manager.js` | Dark/Light/Glass toggle, persists preference |
|
||||||
|
|
||||||
|
### CSS Files
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `SVS-MSP-Calculator.css` | Master import — loads all modules |
|
||||||
|
| `*-tokens.css` | Design tokens — 60+ semantic variables, source of truth |
|
||||||
|
| `*-base.css` | Body, top bar, theme toggle |
|
||||||
|
| `*-layout.css` | Grid, page layout, sidebar, modals |
|
||||||
|
| `*-components.css` | Section cards, buttons, icons, form controls |
|
||||||
|
| `*-responsive.css` | Media queries — 1100px and 600px breakpoints |
|
||||||
|
| `*-light.css` | Light theme — `:root` variable overrides only |
|
||||||
|
| `*-glass.css` | Glass theme — gradients, blur, `color-scheme: dark` |
|
||||||
|
| `*-print.css` | Print/PDF — own token set, aggressive cleanup, A4-ready |
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
254 tests across 47 groups. Custom minimal harness (no framework). Covers:
|
||||||
|
pricing, discounts, add-ons, VoIP tiers, HST, contract terms, edge cases, admin fees.
|
||||||
|
|
||||||
|
Run: `node tests/test-quote-engine.js`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools & When to Use Them
|
||||||
|
|
||||||
|
| Tool | Use when |
|
||||||
|
|------|----------|
|
||||||
|
| **Playwright** | Visual verification — viewing HTML/CSS across themes |
|
||||||
|
| **ui-ux-pro-max** | Any design decision — invoke before touching CSS |
|
||||||
|
| **superpowers** | Planning, parallel agents, debugging, TDD, branch/commit |
|
||||||
|
| **frontend-design** | Building or modifying UI components |
|
||||||
|
| **code-review** | Before marking any task complete |
|
||||||
|
| **code-simplifier** | After implementation — clean up without changing behavior |
|
||||||
|
| **context-mode** | Large output (>20 lines) — routes through sandbox to protect context window. Use `ctx_batch_execute` for multi-command research, `ctx_search` for follow-up queries, `ctx_execute`/`ctx_execute_file` for data processing, `ctx_fetch_and_index` for URL fetching |
|
||||||
|
| **accesslint** | Accessibility and colour contrast checking. Use `contrast-checker` for WCAG ratio checks on hex pairs, `use-of-color` to flag colour-only indicators, `reviewer` for full component audits. Minimum standard: WCAG 2.1 AA (4.5:1 normal text, 3:1 large text). Suggest replacement hex values on failure. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Start
|
||||||
|
|
||||||
|
1. Read `docs/SESSION-HANDOFF.md` — current state of the project
|
||||||
|
2. Run `node tests/test-quote-engine.js` — confirm 254/254
|
||||||
|
3. Ask the user what they want — do not assume, do not start changing things
|
||||||
|
|
||||||
|
Read other docs only when the task requires them:
|
||||||
|
- `docs/QUICK-REF.md` — file map, DOM IDs, pricing constants
|
||||||
|
- `docs/quote-rules.md` — pricing / business logic
|
||||||
|
- `docs/DECISION-LOG.md` — has this decision already been made?
|
||||||
|
- `docs/KNOWN-ISSUES.md` — is this bug already tracked?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session End
|
||||||
|
|
||||||
|
1. Run `node tests/test-quote-engine.js` — all 254 must pass
|
||||||
|
2. Update `docs/SESSION-HANDOFF.md` as a **current state snapshot**:
|
||||||
|
- What is the state of the project right now (not a history log)
|
||||||
|
- What files are in what state
|
||||||
|
- What is next
|
||||||
|
3. Commit if approved by user — one concern per commit
|
||||||
|
|||||||
8
SVS MSP Calculator.code-workspace
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
@@ -97,10 +97,8 @@
|
|||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.12em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
.group-label-sections {
|
.group-label-sections {
|
||||||
opacity: 0.6;
|
|
||||||
letter-spacing: 0.06em;
|
letter-spacing: 0.06em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +204,7 @@
|
|||||||
max-width: none;
|
max-width: none;
|
||||||
text-wrap: pretty;
|
text-wrap: pretty;
|
||||||
}
|
}
|
||||||
.section-title-tag { font-size: 0.9375rem; font-weight: 400; opacity: 0.6; }
|
.section-title-tag { font-size: 0.9375rem; font-weight: 400; }
|
||||||
.section-subtitle {
|
.section-subtitle {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
grid-row: 3;
|
grid-row: 3;
|
||||||
@@ -496,8 +494,8 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
.pill-toggle label .pill-price small { font-size: 0.875rem; opacity: 0.6; }
|
.pill-toggle label .pill-price small { font-size: 0.875rem; }
|
||||||
.pill-toggle label .pill-desc { font-size: 0.875rem; opacity: 0.7; }
|
.pill-toggle label .pill-desc { font-size: 0.875rem; }
|
||||||
.pill-toggle label .pill-savings {
|
.pill-toggle label .pill-savings {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
@@ -1015,7 +1013,7 @@
|
|||||||
with _m IDs and synced by update() via syncEl/syncClass.
|
with _m IDs and synced by update() via syncEl/syncClass.
|
||||||
─────────────────────────────────────────────────────────────── */
|
─────────────────────────────────────────────────────────────── */
|
||||||
.sidebar {
|
.sidebar {
|
||||||
--sidebar-rule-color: var(--sidebar-line-rule);
|
--sidebar-rule-color: var(--sidebar-rule);
|
||||||
--sidebar-copy-size: 0.84375rem;
|
--sidebar-copy-size: 0.84375rem;
|
||||||
--sidebar-copy-line: 1.5;
|
--sidebar-copy-line: 1.5;
|
||||||
--sidebar-note-size: 0.78125rem;
|
--sidebar-note-size: 0.78125rem;
|
||||||
@@ -1114,7 +1112,7 @@
|
|||||||
.sidebar-focus-client-label {
|
.sidebar-focus-client-label {
|
||||||
display: block;
|
display: block;
|
||||||
font-family: 'DM Mono', monospace;
|
font-family: 'DM Mono', monospace;
|
||||||
font-size: 0.625rem;
|
font-size: 0.6875rem;
|
||||||
letter-spacing: 0.14em;
|
letter-spacing: 0.14em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: color-mix(in srgb, var(--ink) 62%, var(--muted));
|
color: color-mix(in srgb, var(--ink) 62%, var(--muted));
|
||||||
@@ -1173,7 +1171,7 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
min-height: 12px;
|
min-height: 12px;
|
||||||
font-family: 'DM Mono', monospace;
|
font-family: 'DM Mono', monospace;
|
||||||
font-size: 0.625rem;
|
font-size: 0.6875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: 0.18em;
|
letter-spacing: 0.18em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -1189,7 +1187,7 @@
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
line-height: var(--sidebar-copy-line);
|
line-height: var(--sidebar-copy-line);
|
||||||
padding: var(--space-stack-tight) var(--space-xs);
|
padding: var(--space-stack-tight) var(--space-xs);
|
||||||
border-bottom: var(--border-thin) var(--sidebar-line-rule-style) var(--sidebar-rule-color);
|
border-bottom: var(--border-thin) var(--sidebar-rule-style) var(--sidebar-rule-color);
|
||||||
border-left: var(--border-medium) solid transparent;
|
border-left: var(--border-medium) solid transparent;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: background var(--transition-fast, 150ms) ease,
|
transition: background var(--transition-fast, 150ms) ease,
|
||||||
@@ -1208,7 +1206,7 @@
|
|||||||
color: var(--text-money);
|
color: var(--text-money);
|
||||||
font-size: var(--sidebar-copy-size);
|
font-size: var(--sidebar-copy-size);
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: var(--text-spacing-money);
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
padding-left: var(--space-sm);
|
padding-left: var(--space-sm);
|
||||||
text-align: right;
|
text-align: right;
|
||||||
@@ -1216,7 +1214,7 @@
|
|||||||
.sidebar-line .lbl-icon { margin-right: var(--space-sm); opacity: 0.82; }
|
.sidebar-line .lbl-icon { margin-right: var(--space-sm); opacity: 0.82; }
|
||||||
.sidebar-mrr-label {
|
.sidebar-mrr-label {
|
||||||
font-family: 'DM Mono', monospace;
|
font-family: 'DM Mono', monospace;
|
||||||
font-size: 0.625rem;
|
font-size: 0.6875rem;
|
||||||
letter-spacing: 0.18em;
|
letter-spacing: 0.18em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -1242,7 +1240,6 @@
|
|||||||
height: 15px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
.sidebar-line-opportunity {
|
.sidebar-line-opportunity {
|
||||||
border-bottom-style: dotted;
|
|
||||||
}
|
}
|
||||||
.sidebar-line-opportunity .val {
|
.sidebar-line-opportunity .val {
|
||||||
color: var(--amber);
|
color: var(--amber);
|
||||||
@@ -1353,7 +1350,6 @@
|
|||||||
}
|
}
|
||||||
#nudgeCounter {
|
#nudgeCounter {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
opacity: 0.55;
|
|
||||||
}
|
}
|
||||||
.nudge-nav-btn {
|
.nudge-nav-btn {
|
||||||
background: var(--surface-ghost);
|
background: var(--surface-ghost);
|
||||||
@@ -1501,7 +1497,7 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
font-family: 'DM Mono', monospace;
|
font-family: 'DM Mono', monospace;
|
||||||
font-size: 8px;
|
font-size: 0.6875rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -1723,7 +1719,7 @@
|
|||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
font-family: 'DM Mono', monospace;
|
font-family: 'DM Mono', monospace;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: var(--text-spacing-money);
|
||||||
padding: var(--space-xs) 0 var(--space-stack) var(--space-2xl);
|
padding: var(--space-xs) 0 var(--space-stack) var(--space-2xl);
|
||||||
}
|
}
|
||||||
.sl-sub-row {
|
.sl-sub-row {
|
||||||
@@ -1755,8 +1751,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Per-user cost section */
|
/* Per-user cost section */
|
||||||
.per-user-cost-sub { display: inline-block; font-size: 0.625rem; opacity: 0.6; font-weight: 400; margin-top: 3px; }
|
.per-user-cost-sub { display: inline-block; font-size: 0.6875rem; font-weight: 400; margin-top: 3px; }
|
||||||
.sidebar-note-mono { font-size: var(--sidebar-mono-size); padding: 2px 0 6px; font-family: 'DM Mono', monospace; line-height: 1.45; }
|
.sidebar-note-mono { font-size: var(--sidebar-mono-size); padding: 2px 0 6px; font-family: 'DM Mono', monospace; line-height: 1.65; }
|
||||||
|
|
||||||
/* VS Comparison block */
|
/* VS Comparison block */
|
||||||
.vs-comparison-wrap {
|
.vs-comparison-wrap {
|
||||||
@@ -1777,7 +1773,7 @@
|
|||||||
margin-top: var(--space-stack);
|
margin-top: var(--space-stack);
|
||||||
padding-top: var(--space-md);
|
padding-top: var(--space-md);
|
||||||
border-top: var(--border-thin) solid var(--border);
|
border-top: var(--border-thin) solid var(--border);
|
||||||
line-height: 1.6;
|
line-height: 1.75;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1814,23 +1810,26 @@
|
|||||||
/* Sidebar utility classes — !important overrides .sidebar-line .val compound selector */
|
/* Sidebar utility classes — !important overrides .sidebar-line .val compound selector */
|
||||||
.sl-muted { color: var(--muted) !important; font-size: 0.6875rem; }
|
.sl-muted { color: var(--muted) !important; font-size: 0.6875rem; }
|
||||||
.sl-discount-val { color: var(--green) !important; }
|
.sl-discount-val { color: var(--green) !important; }
|
||||||
.sl-discount-detail { font-size: 0.7rem; opacity: 0.7; }
|
.sl-discount-detail { font-size: 0.75rem; }
|
||||||
.sl-hst-val { color: var(--text-money) !important; font-size: var(--sidebar-copy-size); }
|
.sl-hst-val { color: var(--text-money) !important; font-size: var(--sidebar-copy-size); }
|
||||||
.sidebar-line-discount { border-bottom-style: dashed; border-bottom-color: var(--sidebar-rule-color); opacity: 0.8; }
|
.sidebar-line-discount { opacity: 0.8; }
|
||||||
.sidebar-line.sidebar-line-hst {
|
.sidebar-line.sidebar-line-hst {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
padding-top: var(--space-stack-tight);
|
padding-top: var(--space-stack-tight);
|
||||||
padding-bottom: var(--space-stack-tight);
|
padding-bottom: var(--space-stack-tight);
|
||||||
border-top: var(--border-thin) var(--sidebar-line-rule-style) var(--sidebar-rule-color);
|
border-top: var(--border-thin) var(--sidebar-rule-style) var(--sidebar-rule-color);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
.sidebar-line.sidebar-line-total {
|
.sidebar-line.sidebar-line-total {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-top: var(--space-sm);
|
margin-top: var(--space-sm);
|
||||||
border-top: var(--border-medium) solid color-mix(in srgb, var(--accent) 30%, transparent);
|
border-top: none;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
padding-top: var(--space-stack-roomy);
|
padding-top: var(--space-stack-roomy);
|
||||||
}
|
}
|
||||||
|
.sidebar-group--monthly .sidebar-line.sidebar-line-total {
|
||||||
|
border-top: var(--border-medium) var(--sidebar-rule-style) var(--sidebar-rule-color);
|
||||||
|
}
|
||||||
.sidebar-line.sidebar-line-total .val {
|
.sidebar-line.sidebar-line-total .val {
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -1840,7 +1839,7 @@
|
|||||||
font-size: 0.7em;
|
font-size: 0.7em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: var(--text-spacing-money);
|
||||||
}
|
}
|
||||||
.sl-hst-toggle {
|
.sl-hst-toggle {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
@@ -1989,7 +1988,7 @@
|
|||||||
}
|
}
|
||||||
.quote-notes-label {
|
.quote-notes-label {
|
||||||
font-family: 'DM Mono', monospace;
|
font-family: 'DM Mono', monospace;
|
||||||
font-size: 10px;
|
font-size: 0.6875rem;
|
||||||
letter-spacing: 0.12em;
|
letter-spacing: 0.12em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -2016,7 +2015,6 @@
|
|||||||
}
|
}
|
||||||
.quote-notes-input::placeholder {
|
.quote-notes-input::placeholder {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
opacity: 0.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── EXPORT BUTTONS ─────────────────────────────────────────────
|
/* ── EXPORT BUTTONS ─────────────────────────────────────────────
|
||||||
|
|||||||
@@ -191,8 +191,7 @@ html {
|
|||||||
--sidebar-zone-value: rgba(99, 216, 162, 0.04);
|
--sidebar-zone-value: rgba(99, 216, 162, 0.04);
|
||||||
--sidebar-zone-summary: rgba(105, 200, 255, 0.03);
|
--sidebar-zone-summary: rgba(105, 200, 255, 0.03);
|
||||||
--sidebar-row-stripe: rgba(105, 200, 255, 0.03);
|
--sidebar-row-stripe: rgba(105, 200, 255, 0.03);
|
||||||
--sidebar-line-rule: rgba(143, 183, 221, 0.12);
|
--sidebar-rule: rgba(143, 183, 221, 0.16);
|
||||||
--sidebar-total-rule: rgba(143, 183, 221, 0.22);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -469,10 +468,10 @@ body::before {
|
|||||||
border-color: var(--glass-divider) !important;
|
border-color: var(--glass-divider) !important;
|
||||||
}
|
}
|
||||||
.sidebar-line {
|
.sidebar-line {
|
||||||
border-bottom-color: var(--glass-divider) !important;
|
border-bottom-color: var(--sidebar-rule) !important;
|
||||||
}
|
}
|
||||||
.sidebar-line.sidebar-line-total {
|
.sidebar-line.sidebar-line-total {
|
||||||
border-top-color: var(--sidebar-total-rule) !important;
|
border-top-color: var(--sidebar-rule) !important;
|
||||||
border-bottom: none !important;
|
border-bottom: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
--ink: #2c2825;
|
--ink: #2c2825;
|
||||||
--paper: #e2dccf;
|
--paper: #e2dccf;
|
||||||
--accent: #637f88;
|
--accent: #637f88;
|
||||||
--muted: #6a6157;
|
--muted: #554e46;
|
||||||
--border: #c3baab;
|
--border: #c3baab;
|
||||||
--border-soft: #cdc6ba;
|
--border-soft: #cdc6ba;
|
||||||
--card: #ece4d6;
|
--card: #ece4d6;
|
||||||
@@ -119,8 +119,7 @@
|
|||||||
--sidebar-zone-value: rgba(33, 112, 69, 0.04);
|
--sidebar-zone-value: rgba(33, 112, 69, 0.04);
|
||||||
--sidebar-zone-summary: rgba(0, 0, 0, 0.02);
|
--sidebar-zone-summary: rgba(0, 0, 0, 0.02);
|
||||||
--sidebar-row-stripe: rgba(0, 0, 0, 0.02);
|
--sidebar-row-stripe: rgba(0, 0, 0, 0.02);
|
||||||
--sidebar-line-rule: color-mix(in srgb, var(--border) 70%, transparent);
|
--sidebar-rule: color-mix(in srgb, var(--border) 75%, transparent);
|
||||||
--sidebar-total-rule: color-mix(in srgb, var(--border) 90%, transparent);
|
|
||||||
--surface-switch-off: #b5ad9f;
|
--surface-switch-off: #b5ad9f;
|
||||||
--surface-switch-on: var(--green);
|
--surface-switch-on: var(--green);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -156,7 +156,7 @@
|
|||||||
.vs-brand-name { font-size: 0.9375rem; }
|
.vs-brand-name { font-size: 0.9375rem; }
|
||||||
.vs-table td { padding: var(--space-sm) 3px; font-size: 0.78125rem; }
|
.vs-table td { padding: var(--space-sm) 3px; font-size: 0.78125rem; }
|
||||||
.vs-save-row td { padding: var(--space-stack-tight) var(--space-md); }
|
.vs-save-row td { padding: var(--space-stack-tight) var(--space-md); }
|
||||||
.vs-footnote { font-size: 0.65625rem; line-height: 1.55; }
|
.vs-footnote { font-size: 0.65625rem; line-height: 1.7; }
|
||||||
|
|
||||||
/* Pitch footer */
|
/* Pitch footer */
|
||||||
.pitch-wrap { padding: 0; }
|
.pitch-wrap { padding: 0; }
|
||||||
|
|||||||
@@ -87,10 +87,11 @@
|
|||||||
--text-copy-line: 1.76;
|
--text-copy-line: 1.76;
|
||||||
--text-compact-line: 1.58;
|
--text-compact-line: 1.58;
|
||||||
--text-title-line: 1.24;
|
--text-title-line: 1.24;
|
||||||
|
--text-spacing-money: 0.05em;
|
||||||
--ink: #e8e3da; /* warm beige-white — brighter for legibility */
|
--ink: #e8e3da; /* warm beige-white — brighter for legibility */
|
||||||
--paper: #1c1a17; /* darker base — widens gap vs card for panel float */
|
--paper: #1c1a17; /* darker base — widens gap vs card for panel float */
|
||||||
--accent: #3d8aba; /* lifted blue — pops on dark backgrounds */
|
--accent: #3d8aba; /* lifted blue — pops on dark backgrounds */
|
||||||
--muted: #9e9588; /* softer secondary — clearly subordinate but readable */
|
--muted: #b0a99f; /* softer secondary — clearly subordinate but readable (6.6:1) */
|
||||||
--border: #35322c; /* subtler dividers */
|
--border: #35322c; /* subtler dividers */
|
||||||
--border-soft: var(--border);
|
--border-soft: var(--border);
|
||||||
--card: #272420; /* elevated surface — clear separation from paper */
|
--card: #272420; /* elevated surface — clear separation from paper */
|
||||||
@@ -260,10 +261,8 @@
|
|||||||
--sidebar-zone-value: rgba(58, 184, 112, 0.05);
|
--sidebar-zone-value: rgba(58, 184, 112, 0.05);
|
||||||
--sidebar-zone-summary: rgba(255, 255, 255, 0.03);
|
--sidebar-zone-summary: rgba(255, 255, 255, 0.03);
|
||||||
--sidebar-zone-tax: transparent;
|
--sidebar-zone-tax: transparent;
|
||||||
--sidebar-line-rule: color-mix(in srgb, var(--border) 88%, transparent);
|
--sidebar-rule: color-mix(in srgb, var(--border) 75%, transparent);
|
||||||
--sidebar-line-rule-style: dashed;
|
--sidebar-rule-style: dashed;
|
||||||
--sidebar-total-rule: var(--border);
|
|
||||||
--sidebar-total-rule-style: solid;
|
|
||||||
--sidebar-row-stripe: rgba(255, 255, 255, 0.018);
|
--sidebar-row-stripe: rgba(255, 255, 255, 0.018);
|
||||||
--sidebar-group-title-color: var(--muted);
|
--sidebar-group-title-color: var(--muted);
|
||||||
--sidebar-stack-gap: 14px;
|
--sidebar-stack-gap: 14px;
|
||||||
|
|||||||
BIN
dark-footnote-after.png
Normal file
|
After Width: | Height: | Size: 9.5 KiB |
BIN
dark-sidebar-after.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
dark-theme-full.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
@@ -1,400 +0,0 @@
|
|||||||
# SVS MSP CALC — Beta Build Checkpoint
|
|
||||||
|
|
||||||
**Date:** 2026-03-15
|
|
||||||
**Status:** Phases 1–8 + 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: 5` → `8` |
|
|
||||||
| 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 I–III)
|
|
||||||
|
|
||||||
| # | 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 I–III
|
|
||||||
- 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 I–III component CSS
|
|
||||||
- Sidebar renders correctly across all 4 themes at all breakpoints
|
|
||||||
|
|
||||||
### Phase 3: UX Hardening (Sections I–III)
|
|
||||||
|
|
||||||
#### 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 I–III 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` / `#a34a14` → `var(--sky)` + `color-mix()` |
|
|
||||||
| CQ6 | Dead null-check removed | quote-render.js:533 | `nudgeIndex == null ||` removed — `nudgeIndex` is always initialized to `0` |
|
|
||||||
|
|
||||||
**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 524–551).
|
|
||||||
|
|
||||||
**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.15s` → `var(--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.25s` → `var(--transition-medium)` | components.css | sec-chevron transform, collapsible-toggle transform, nudge-banner bg/border |
|
|
||||||
| T4 | 1× `0.15s` → `var(--transition-fast)` | base.css | theme-toggle-btn |
|
|
||||||
| T5 | 2× `0.15s` → `var(--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 `!important` — `body.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:**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"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 IV–VI unchanged (deferred)
|
|
||||||
@@ -1,415 +0,0 @@
|
|||||||
# SVS MSP CALC — Master Session Prompt
|
|
||||||
### Pre-Alpha → Beta Optimization Brief
|
|
||||||
**Version:** 1.0 | **Date:** 2026-03-14 | **Audience:** Senior Engineers + UI/UX Leads
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WHO YOU ARE (Team Persona)
|
|
||||||
|
|
||||||
You are a cross-functional senior engineering and design team operating with full production standards:
|
|
||||||
|
|
||||||
- **Senior Frontend Engineer** — Deep HTML/CSS/JS expertise. Minimal safe changes. Surgical precision. Strong regression awareness. No casual hacks, no premature abstractions, no over-engineering.
|
|
||||||
- **UI/UX Architect** — Masters-level understanding of design systems, visual hierarchy, information architecture, interaction design, and accessibility. Fluent in tokenized CSS design systems. Makes layouts feel inevitable, not assembled.
|
|
||||||
- **Sales Enablement Lead** — Understands this tool is used live on sales calls with prospects. Every UX decision must serve the sales conversation. Copy must be confident, concise, and client-facing.
|
|
||||||
- **QA Engineer** — Regression-aware. Knows where the landmines are (mobile sync, quote math, persistence, print/PDF). Validates behavior before marking work done.
|
|
||||||
|
|
||||||
You do not introduce change for its own sake. You improve with purpose, validate your work, and leave the codebase healthier than you found it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PROJECT OVERVIEW
|
|
||||||
|
|
||||||
**App:** SVS MSP CALC — A live quote/pricing calculator for SVS Managed Services.
|
|
||||||
**Type:** Static HTML + Vanilla JS + Modular CSS (no frameworks, no build tools, no npm)
|
|
||||||
**Used by:** SVS sales team, live on screen with prospects during discovery calls
|
|
||||||
**Goal of this session:** Advance from pre-alpha to a solid, ship-ready **beta build**
|
|
||||||
|
|
||||||
### What "Beta" Means Here
|
|
||||||
- All active sections (I–III) are visually polished and production-quality
|
|
||||||
- All three themes (Dark, Light, Glass) are consistent, tested, and professional
|
|
||||||
- Mobile experience is smooth, reliable, and parity-complete with desktop
|
|
||||||
- Print/PDF invoice is clean, branded, and pixel-accurate
|
|
||||||
- Quote math, persistence, and export are verified and regression-safe
|
|
||||||
- UX hierarchy is clear: users know exactly what to do without instructions
|
|
||||||
- No known bugs in active scope
|
|
||||||
- Sections IV–VI (Zero Trust Networking, VoIP) are ready to activate — code is solid and UX frameworks are in place, even if content is still gated
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ARCHITECTURE SNAPSHOT
|
|
||||||
|
|
||||||
```
|
|
||||||
svsmspcalc/
|
|
||||||
├── SVS-MSP-Calculator.html # Stable HTML shell (65KB, inline handlers)
|
|
||||||
├── SVS-MSP-Calculator.js # Master orchestration (310 lines)
|
|
||||||
├── quote-engine.js # Pure quote math (196 lines)
|
|
||||||
├── quote-pricing.js # Pricing defaults + JSON override (134 lines)
|
|
||||||
├── quote-render.js # DOM rendering + nudge engine (662 lines)
|
|
||||||
├── quote-persistence.js # localStorage save/restore (212 lines)
|
|
||||||
├── quote-export.js # Print/PDF + JSON export (295 lines)
|
|
||||||
├── theme-manager.js # Dark/Light/Glass switching (110 lines)
|
|
||||||
├── mobile-sync.js # Mobile panel + 100+ element sync (236 lines)
|
|
||||||
├── SVS-MSP-Calculator.css # Manifest: @imports all CSS files
|
|
||||||
├── SVS-MSP-Calculator-tokens.css # Design tokens + CSS vars
|
|
||||||
├── SVS-MSP-Calculator-base.css # Global chrome
|
|
||||||
├── SVS-MSP-Calculator-layout.css # Grid, header, main/sidebar split
|
|
||||||
├── SVS-MSP-Calculator-components.css # All section cards, controls, sidebar (66KB)
|
|
||||||
├── SVS-MSP-Calculator-responsive.css # Viewport/container overrides (16KB)
|
|
||||||
├── SVS-MSP-Calculator-print.css # Print-specific rules
|
|
||||||
├── SVS-MSP-Calculator-light.css # Light theme overrides
|
|
||||||
├── SVS-MSP-Calculator-glass.css # Glass theme (glassmorphism)
|
|
||||||
├── package-prices.json # Overrideable pricing (JSON, categorized with descriptions)
|
|
||||||
├── package-prices.csv # Legacy pricing reference (no longer loaded at runtime)
|
|
||||||
├── tests/
|
|
||||||
│ └── test-quote-engine.js # Automated quote engine tests (88 tests, Node.js)
|
|
||||||
└── docs/
|
|
||||||
├── README.md
|
|
||||||
├── ai-session-brief.md
|
|
||||||
├── phase-roadmap.md
|
|
||||||
├── code-verification.md
|
|
||||||
├── quote-rules.md
|
|
||||||
├── regression-checklist.md
|
|
||||||
└── MASTER-SESSION-PROMPT.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tech Stack Facts
|
|
||||||
- **No frameworks.** Vanilla JS (ES5-compatible), HTML5, CSS3.
|
|
||||||
- **No build tools.** Open the HTML in a browser — it runs.
|
|
||||||
- **No npm.** No webpack, Vite, Rollup, Parcel, or transpilation.
|
|
||||||
- **No TypeScript.** Plain `.js` files.
|
|
||||||
- **CSS architecture:** Tokenized custom properties. Modular files. Three theme override layers (Dark, Light, Glass).
|
|
||||||
- **State:** localStorage only. Key: `svs-msp-quote-v1`.
|
|
||||||
- **Fonts:** Google Fonts (Cinzel, Poppins, Lato, DM Mono).
|
|
||||||
- **Icons:** Font Awesome 7 Sharp (local SVGs), M365 icon set (local).
|
|
||||||
|
|
||||||
### Initialization Flow
|
|
||||||
```
|
|
||||||
initTheme() → restore saved theme (dark/light/glass)
|
|
||||||
initQuote() → load JSON pricing, set quote ref, restore localStorage, call update()
|
|
||||||
update() → calcQuote() → renderQuoteUi() → renderSidebar() → nudges → savings → summaries
|
|
||||||
debouncedSave() → auto-save to localStorage (debounced 400ms)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automated Testing
|
|
||||||
```
|
|
||||||
node svsmspcalc/tests/test-quote-engine.js
|
|
||||||
```
|
|
||||||
254 tests, zero dependencies. Tests the pure `calculateQuote(state, pricing)` function against known-good expected values using default pricing. Covers: rates, add-ons, admin fee logic, discounts, HST, VoIP, ZT networking, edge cases, MRR integrity, export schema, persistence shape, import mapping, and quote output invariants.
|
|
||||||
|
|
||||||
Run after any change to `quote-engine.js`, `quote-pricing.js`, or pricing JSON values.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ACTIVE SECTIONS (I–VI) — All Structurally Active
|
|
||||||
|
|
||||||
| # | Section | Display Order | Key Logic |
|
|
||||||
|---|---------|---------------|-----------|
|
|
||||||
| I | User Package | 1st | M365 Included ($130/user) vs BYOL ($110/user); 4 add-ons |
|
|
||||||
| II | Endpoint Package | 2nd | $35/endpoint; USB Blocking + Bare Metal Backup add-ons |
|
|
||||||
| III | Site Management | 3rd | Floor + minimum threshold; ZT supplement; 1PWM surcharge; waivable |
|
|
||||||
| IV | Server Management | 4th | $120/server |
|
|
||||||
| V | Zero Trust Networking (HaaS) | 5th | ZT seats + routers |
|
|
||||||
| VI | VoIP / Unified Communications (UCaaS) | 6th | 3 tiers + desk phone + eFax |
|
|
||||||
| — | Contract & Onboarding | Settings bar | M2M / 12mo (3%) / 24mo (5%); onboarding auto-calc or manual; HST 13% |
|
|
||||||
|
|
||||||
All sections use unified `sec-controls-row` header layout: stepper + label badge + price badge.
|
|
||||||
Sections default to collapsed. Inner collapsibles default to collapsed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## KEY PRICING CONSTANTS
|
|
||||||
|
|
||||||
```js
|
|
||||||
RATE_M365: 130 // per user/mo — M365 Included license
|
|
||||||
RATE_BYOL: 110 // per user/mo — Bring Your Own License
|
|
||||||
RATE_ENDPOINT: 35 // per endpoint/mo
|
|
||||||
RATE_SERVER: 120 // per server/mo
|
|
||||||
ADMIN_FEE_FLOOR: 150
|
|
||||||
ADMIN_FEE_MINIMUM: 650
|
|
||||||
DISCOUNT_12MO: 0.03 // 3%
|
|
||||||
DISCOUNT_24MO: 0.05 // 5%
|
|
||||||
HST_RATE: 0.13 // Ontario
|
|
||||||
VOIP_RATE_BASIC: 28 // per seat/mo
|
|
||||||
VOIP_RATE_STANDARD: 35
|
|
||||||
VOIP_RATE_PREMIUM: 45
|
|
||||||
```
|
|
||||||
|
|
||||||
Pricing can be overridden at runtime via `package-prices.json`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## NON-NEGOTIABLES (Hard Constraints)
|
|
||||||
|
|
||||||
These are inviolable. Every change must preserve them:
|
|
||||||
|
|
||||||
1. **HTML shell is stable.** DOM IDs are a contract. Mobile sync maps 100+ ID pairs (desktop ↔ `_m` suffix). Renaming an ID breaks sync silently and catastrophically.
|
|
||||||
2. **Quote math is correct.** Any change to `quote-engine.js` requires before/after validation of all outputs. Do not "clean up" math without proving equivalence.
|
|
||||||
3. **localStorage persistence is intact.** `saveState()` and `restoreState()` must round-trip cleanly. Verify after any form/state changes.
|
|
||||||
4. **All three themes must work.** Dark (default), Light (soft khaki), Glass (glassmorphism). Changes to tokens or components cascade to all three.
|
|
||||||
5. **Mobile parity is maintained.** The sidebar clone in the mobile panel must stay in sync. Mobile UX must be usable on a 375px viewport.
|
|
||||||
6. **Print/PDF export must be tested after CSS changes.** Print CSS lives in a separate file but is sensitive to component class changes.
|
|
||||||
7. **No framework or build-tool migration.** This is vanilla JS by design.
|
|
||||||
8. **No broad rewrites.** Surgical, approved changes only.
|
|
||||||
9. **Sections IV–VI remain deferred** unless explicitly reopened.
|
|
||||||
10. **Read before editing.** Always inspect the current code before making changes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## BETA WORK PRIORITIES
|
|
||||||
|
|
||||||
Work in this order unless directed otherwise. Each priority includes UX, code, and QA dimensions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 1 — Visual Design System Audit & Elevation
|
|
||||||
|
|
||||||
**Goal:** Achieve visual cohesion and professional polish across all themes that rivals a SaaS product.
|
|
||||||
|
|
||||||
**UI/UX Audit Checklist:**
|
|
||||||
- [ ] Typography hierarchy — Is Cinzel/Poppins/Lato used consistently? Are font sizes, weights, and line-heights harmonious across sections?
|
|
||||||
- [ ] Spacing system — Is padding/margin using tokens consistently? Are section cards visually balanced?
|
|
||||||
- [ ] Color usage — Are `--accent`, `--green`, `--amber`, `--muted` used purposefully, not decoratively?
|
|
||||||
- [ ] Interactive states — Do all buttons, inputs, toggles, and checkboxes have clear hover/focus/active states in all three themes?
|
|
||||||
- [ ] Card hierarchy — Is there a clear visual distinction between level-1 containers, level-2 cards, and level-3 controls?
|
|
||||||
- [ ] Icon consistency — Are Font Awesome icons used at consistent sizes, weights, and optical alignment?
|
|
||||||
- [ ] Section header design — Are the numbered section headers (I, II, III) visually strong and scannable?
|
|
||||||
- [ ] Sidebar layout — Does the sidebar feel like a polished financial summary panel, not a DOM dump?
|
|
||||||
- [ ] Progress bars — Are the progress bars in Section I legible and purposeful?
|
|
||||||
- [ ] Nudge panel — Does the nudge carousel feel like a smart sales assistant, not a popup?
|
|
||||||
|
|
||||||
**CSS Architecture Health:**
|
|
||||||
- [ ] Are there redundant/conflicting rules in `components.css` vs `responsive.css`?
|
|
||||||
- [ ] Are design tokens being used everywhere they should be, or are magic numbers scattered?
|
|
||||||
- [ ] Is the Glass theme consistent and not broken at any viewport?
|
|
||||||
- [ ] Is the Light theme soft and readable without feeling washed out?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 2 — Interaction Design & UX Flow
|
|
||||||
|
|
||||||
**Goal:** The tool should feel intuitive, responsive, and professionally crafted to a prospect sitting across the table.
|
|
||||||
|
|
||||||
**Interaction Audit:**
|
|
||||||
- [ ] **Onboarding flow clarity** — When a user opens the tool fresh, is the first action obvious?
|
|
||||||
- [ ] **Section collapse/expand** — Is the animation smooth? Are collapsed section summaries accurate and helpful?
|
|
||||||
- [ ] **Add-on toggle behavior** — Does toggling add-ons give clear feedback (visual state + sidebar update)?
|
|
||||||
- [ ] **Stepper inputs** — Do +/- steppers feel snappy? Is there clear min/max clamping behavior?
|
|
||||||
- [ ] **Contract term selection** — Is the 3-option selector clearly communicating the discount impact?
|
|
||||||
- [ ] **Onboarding fee override** — Is the manual override UX clear (placeholder, lock icon, restore-to-auto)?
|
|
||||||
- [ ] **Admin fee waiver** — Is the waiver checkbox prominent enough? Is the consequence visible immediately?
|
|
||||||
- [ ] **Sidebar update speed** — Is the live update fast enough to feel real-time?
|
|
||||||
- [ ] **Nudge carousel** — Is the 30-second rotation appropriate? Does it feel helpful or distracting?
|
|
||||||
- [ ] **Theme toggle** — Is the toggle clearly labeled for current/next state? Is the transition smooth?
|
|
||||||
- [ ] **Reset confirmation modal** — Is it clear what gets destroyed? Is the secondary action (cancel) prominent?
|
|
||||||
- [ ] **Export buttons** — Are Print and JSON export clearly labeled and discoverable?
|
|
||||||
|
|
||||||
**Keyboard & Accessibility:**
|
|
||||||
- [ ] All interactive elements reachable via Tab in logical order
|
|
||||||
- [ ] Focus ring visible in all themes
|
|
||||||
- [ ] ARIA labels accurate and present on all form controls
|
|
||||||
- [ ] Color contrast ratios pass WCAG AA in Light theme (most at-risk)
|
|
||||||
- [ ] Screen reader order matches visual order
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 3 — Responsive Design Hardening
|
|
||||||
|
|
||||||
**Goal:** The layout should feel elastically fluid at every breakpoint, not brittle or hacked together.
|
|
||||||
|
|
||||||
**Breakpoint Audit:**
|
|
||||||
- [ ] 1800px+ — Does the max-width container feel appropriately constrained?
|
|
||||||
- [ ] 1400-1800px — Desktop sweet spot. Is the 3:2 main/sidebar split balanced?
|
|
||||||
- [ ] 1100-1400px — Narrower desktop. Do section cards reflow without overlapping?
|
|
||||||
- [ ] 780-1100px — Tablet. Does the layout gracefully switch to sidebar-as-panel?
|
|
||||||
- [ ] 480-780px — Mobile-landscape/small tablet. Single column, all controls accessible?
|
|
||||||
- [ ] 375-480px — Small mobile. Is the floating MRR pill positioned correctly? Does the bottom sheet open cleanly?
|
|
||||||
- [ ] 320px — Edge case. Does anything catastrophically break?
|
|
||||||
|
|
||||||
**Responsive Rules:**
|
|
||||||
- Prefer `clamp()`, container queries, and token-based adjustments over stacked `@media` hacks
|
|
||||||
- Section cards should never clip or overflow their container
|
|
||||||
- Contract/onboarding settings must adapt before content gets squeezed
|
|
||||||
- Touch targets must be ≥44px on mobile
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 4 — Quote Engine & Business Logic Verification
|
|
||||||
|
|
||||||
**Goal:** Every line-item and total in the sidebar is mathematically correct and matches what the invoice shows.
|
|
||||||
|
|
||||||
**Verification Matrix (test each combination):**
|
|
||||||
|
|
||||||
| Test | What to Verify |
|
|
||||||
|------|---------------|
|
|
||||||
| 0 users, 1 endpoint | Admin fee floor triggers correctly |
|
|
||||||
| 5 users M365, 3 endpoints, no add-ons | Base MRR matches manual calculation |
|
|
||||||
| 5 users BYOL + 1Password + Zero Trust | Add-on stacking is correct |
|
|
||||||
| 12-month term | 3% discount applied to base MRR only (not admin or HST) |
|
|
||||||
| 24-month term | 5% discount; onboarding auto-waived |
|
|
||||||
| Manual onboarding override | Auto-calc disabled; manual value preserved on save/restore |
|
|
||||||
| HST toggle | 13% applied only to MRR+onboarding (not before); first invoice total correct |
|
|
||||||
| Admin fee waiver | Admin fee = $0; "Value Unlocked" reflects waived amount |
|
|
||||||
| VoIP Basic 3 seats + 2 desk phones | VoIP cost correct; eFax add-on stacks |
|
|
||||||
| Full configuration (all add-ons, 24mo, HST) | All components sum correctly; no double-counting |
|
|
||||||
| Save → Reload | All values restore exactly; no drift |
|
|
||||||
| Export JSON → parse | All keys present; math cross-checks against UI |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 5 — Mobile Experience Completeness
|
|
||||||
|
|
||||||
**Goal:** A salesperson can hand their phone to a prospect and the quote tool is fully usable.
|
|
||||||
|
|
||||||
**Mobile Audit:**
|
|
||||||
- [ ] Floating MRR pill — correct value, correct position, visible in all themes
|
|
||||||
- [ ] Bottom sheet panel opens smoothly (slide-up animation clean)
|
|
||||||
- [ ] Scroll lock applied correctly when panel open (body doesn't scroll behind)
|
|
||||||
- [ ] All sidebar elements clone correctly into mobile panel (100+ pairs)
|
|
||||||
- [ ] HST toggle syncs bidirectionally (desktop ↔ mobile)
|
|
||||||
- [ ] Quote values in mobile panel match desktop exactly
|
|
||||||
- [ ] Close gestures (× button, Escape key, backdrop tap) all work
|
|
||||||
- [ ] Print/Export buttons accessible from mobile panel
|
|
||||||
- [ ] No clipped text or overflowing elements in mobile panel
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 6 — Print/PDF Invoice Quality
|
|
||||||
|
|
||||||
**Goal:** The printed invoice looks like it came from a professional MSP's billing system, not an HTML form.
|
|
||||||
|
|
||||||
**Print Audit:**
|
|
||||||
- [ ] Logo renders correctly (embedded SVG, not broken)
|
|
||||||
- [ ] Client name, quote ref, and date are on every page header
|
|
||||||
- [ ] All line items appear with correct labels and amounts
|
|
||||||
- [ ] Feature checklist is legible and cleanly formatted
|
|
||||||
- [ ] Totals section is visually prominent
|
|
||||||
- [ ] Value unlocked section is formatted correctly
|
|
||||||
- [ ] Comparison (vs. in-house IT) is present and readable
|
|
||||||
- [ ] No phantom scrollbars or UI chrome bleeds into print
|
|
||||||
- [ ] Page breaks don't split line-item rows awkwardly
|
|
||||||
- [ ] Fonts render in print (or fallback gracefully)
|
|
||||||
- [ ] All themes produce same print output (print CSS is theme-independent)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 7 — Sections IV–VI: Beta-Ready Scaffolding
|
|
||||||
|
|
||||||
**Goal:** Even if Sections IV–VI remain content-gated, their code, UX framework, and CSS should be clean enough to activate with minimal effort.
|
|
||||||
|
|
||||||
**Audit:**
|
|
||||||
- [ ] Section IV (Zero Trust Networking) — Is the HTML structure complete? Are inputs wired to `readFormState()`? Does the engine include ZT costs?
|
|
||||||
- [ ] Section V (VoIP/UCaaS) — Are all 3 tier selectors functional? Desk phone HaaS wired? eFax add-on wired? Savings comparison functional?
|
|
||||||
- [ ] Section VI — Is it a clean placeholder with no broken references?
|
|
||||||
- [ ] Deferred section CSS — Are styles isolated so activating them doesn't cause layout contamination?
|
|
||||||
- [ ] Quote engine — Does it calculate ZT/VoIP costs when those inputs are present, even if sections are hidden?
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### PRIORITY 8 — Code Quality & Documentation Sync
|
|
||||||
|
|
||||||
**Goal:** Any engineer should be able to pick up this codebase in 10 minutes and understand what's happening.
|
|
||||||
|
|
||||||
**Code Quality Checklist:**
|
|
||||||
- [ ] No dead code in active modules (check for unused functions, unreachable branches)
|
|
||||||
- [ ] No magic numbers — pricing constants should reference `DEFAULTS` keys, not hardcoded values
|
|
||||||
- [ ] No duplicate logic between `quote-engine.js` and `quote-render.js`
|
|
||||||
- [ ] `mobile-sync.js` sync map is clean — no stale ID pairs for removed elements
|
|
||||||
- [ ] CSS custom properties used consistently — no redundant fallback values where tokens are sufficient
|
|
||||||
- [ ] `quote-export.js` HTML template is clean — no CSS class references that no longer exist
|
|
||||||
- [ ] `SVS-MSP-Calculator.js` `update()` function is linear and readable — no side effects that aren't obvious
|
|
||||||
|
|
||||||
**Documentation Sync:**
|
|
||||||
- [ ] `docs/README.md` reflects current file structure and phase status
|
|
||||||
- [ ] `docs/code-verification.md` has been updated with latest known-good state
|
|
||||||
- [ ] `docs/regression-checklist.md` covers all active sections and export paths
|
|
||||||
- [ ] `docs/quote-rules.md` reflects current pricing and business rule implementation
|
|
||||||
- [ ] `docs/phase-roadmap.md` is updated to reflect beta completion criteria
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## WORKING PROTOCOL FOR THIS SESSION
|
|
||||||
|
|
||||||
### Before Making Any Change
|
|
||||||
1. Read the relevant file(s) first — do not edit from memory
|
|
||||||
2. Identify the minimal change that achieves the goal
|
|
||||||
3. Check if the change touches any of the regression hotspots (see below)
|
|
||||||
4. Confirm the change won't break mobile sync, theme cascade, or persistence
|
|
||||||
|
|
||||||
### Regression Hotspots — Extra Caution Required
|
|
||||||
| Area | Risk | Why |
|
|
||||||
|------|------|-----|
|
|
||||||
| `quote-engine.js` math | Critical | Business-critical; errors generate wrong quotes in real sales calls |
|
|
||||||
| localStorage round-trip | High | Silent failures; user loses configured quote |
|
|
||||||
| Mobile sync ID map | High | 100+ pairs; silently desyncs if IDs change |
|
|
||||||
| Print/PDF CSS | Medium | Separate cascade; component class changes cascade here |
|
|
||||||
| Theme switching | Medium | All four themes affected by token/component changes |
|
|
||||||
| `update()` call chain | Medium | Side effects in render sequence can cascade silently |
|
|
||||||
|
|
||||||
### After Making Changes
|
|
||||||
1. Verify syntax (especially JS — no console errors on load)
|
|
||||||
2. Check all three themes render correctly
|
|
||||||
3. Check mobile panel renders correctly at ≤780px
|
|
||||||
4. Verify sidebar totals are mathematically correct for a test quote
|
|
||||||
5. If CSS touched: verify print output is unaffected
|
|
||||||
6. Update `docs/code-verification.md` if a known-good state changes
|
|
||||||
|
|
||||||
### Commit Protocol
|
|
||||||
- Commits should be small and focused (one concern per commit)
|
|
||||||
- Commit messages should state what changed and why (not just "fix css")
|
|
||||||
- Do not combine unrelated changes in one commit
|
|
||||||
- Do not commit `.bak-focusmode` files
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UX/UI DESIGN PRINCIPLES FOR THIS PROJECT
|
|
||||||
|
|
||||||
These principles guide every UI decision:
|
|
||||||
|
|
||||||
1. **Sales clarity over visual novelty.** If a prospect can't read a number at a glance, the design failed.
|
|
||||||
2. **Trust through polish.** A janky tooltip or misaligned input erodes prospect confidence. Every pixel matters.
|
|
||||||
3. **Progressive disclosure.** Lead with the total MRR. Let detail unfold as needed (collapsed sections, sidebar).
|
|
||||||
4. **Reduce cognitive load.** Group related controls. Use spacing and color to indicate hierarchy, not decoration.
|
|
||||||
5. **Feedback immediacy.** Every input change must visibly update the sidebar within 1 frame. No lag.
|
|
||||||
6. **Consistency as reliability.** Spacing, typography, and color behavior should be predictable. Surprises feel like bugs.
|
|
||||||
7. **Dark theme is the flagship.** Light and Glass must meet the same bar. No theme should feel like a degraded experience.
|
|
||||||
8. **Mobile is a first-class use case.** A sales rep on a tablet or phone must be able to run a full quote.
|
|
||||||
9. **The sidebar is the hero.** It's where the value proposition lives. Design it with the weight of a financial summary.
|
|
||||||
10. **Copy is UI.** Labels, nudges, section headers, and button text are all UX. Make them purposeful and confident.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## FILE READ ORDER FOR NEW SESSIONS
|
|
||||||
|
|
||||||
To resume efficiently, read in this order:
|
|
||||||
1. `docs/README.md` — current project state
|
|
||||||
2. `docs/phase-roadmap.md` — approved work and constraints
|
|
||||||
3. `docs/code-verification.md` — known-good baseline
|
|
||||||
4. `docs/MASTER-SESSION-PROMPT.md` — this file (if not already loaded)
|
|
||||||
5. Then only the source files relevant to the specific task
|
|
||||||
|
|
||||||
For business logic questions: `docs/quote-rules.md`
|
|
||||||
For manual QA: `docs/regression-checklist.md`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## BETA DEFINITION OF DONE
|
|
||||||
|
|
||||||
The build is ready to call "beta" when:
|
|
||||||
|
|
||||||
- [ ] All Sections I–III are visually polished and functionally complete
|
|
||||||
- [ ] All three themes pass a full visual review at all major breakpoints
|
|
||||||
- [ ] Print/PDF invoice is clean and professionally branded
|
|
||||||
- [ ] Mobile panel is fully synced and usable at 375px
|
|
||||||
- [ ] Quote math passes all combinations in the verification matrix
|
|
||||||
- [ ] localStorage save/restore is tested and clean
|
|
||||||
- [ ] JSON export is valid and complete
|
|
||||||
- [ ] All nudges fire correctly for their trigger conditions
|
|
||||||
- [ ] Section collapse/expand summaries are accurate
|
|
||||||
- [ ] Comparison (vs. in-house IT) and VoIP savings panels show correct values
|
|
||||||
- [ ] No console errors on fresh load in any theme
|
|
||||||
- [ ] All docs are updated and accurate
|
|
||||||
- [ ] Sections IV–VI are scaffolded cleanly, ready to activate on demand
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*This document is the canonical session brief for the SVS MSP CALC beta push. Update it when constraints, priorities, or decisions change.*
|
|
||||||
@@ -1,86 +1,67 @@
|
|||||||
# Session Handoff — SVS MSP CALC
|
# Session Handoff — SVS MSP CALC
|
||||||
|
|
||||||
**Last updated:** 2026-03-16
|
**Last updated:** 2026-03-18
|
||||||
**Session:** Spacing Token Consolidation + Docs Cleanup + Phase 4-6 Research
|
**Session:** Sidebar UX Polish + Readability Pass
|
||||||
**Status:** COMMITTED — token consolidation + docs cleanup merged. Phase 4-6 research complete, design not started.
|
**Status:** Sidebar dividers unified, letter-spacing improved, contrast & font floor applied. Committed.
|
||||||
**Tests:** 254/254 passing
|
**Tests:** 254/254 passing
|
||||||
|
|
||||||
## What Was Done This Session
|
## Current State
|
||||||
|
|
||||||
### Spacing Token Consolidation (Phases 1-3) — COMMITTED
|
### What's Committed
|
||||||
Commit `786a850`: `css: tokenize borders, spacing, and radii — spacing audit pass 4`
|
- **Sidebar divider unification** — one `--sidebar-rule` token + `dashed` style everywhere. Old tokens removed: `--sidebar-line-rule`, `--sidebar-line-rule-style`, `--sidebar-total-rule`, `--sidebar-total-rule-style`
|
||||||
- Phase 1: border widths (76 replacements), Phase 2: spacing tokens (18), Phase 3: border-radius (16)
|
- **Total line scoped** — border-top only on `.sidebar-group--monthly .sidebar-line-total`, removed from invoice/value/summary
|
||||||
- 110 replacements + 7 new tokens across 7 CSS files
|
- **Letter-spacing token** — `--text-spacing-money: 0.05em` (was hardcoded 0.02em), applied to `.val`, `.sl-sub`, `.suffix-mo`
|
||||||
- Playwright verified: 9 screenshots (3 themes x 3 viewports) — zero regressions
|
- **Font size floor** — 6 locations bumped from 8px/10px/0.625rem → 0.6875rem (11px min)
|
||||||
|
- **Muted contrast improved** — Dark `#9e9588`→`#b0a99f` (6.6:1), Light `#6a6157`→`#554e46` (6.5:1)
|
||||||
|
- **Opacity cleanup** — removed opacity from 9 text elements that were double-muting with `--muted`
|
||||||
|
- **Sidebar fine-print line-height** — `.vs-footnote` 1.75, `.sidebar-note-mono` 1.65, responsive 1.7
|
||||||
|
|
||||||
### Docs Cleanup — COMMITTED
|
### What's NOT Committed (still modified)
|
||||||
Commit `a42ce66`: removed 11 outdated files (1,480 lines)
|
- `CLAUDE.md` — updated from beta
|
||||||
- 8 STAGE*-SESSION-PROMPT.md files, ai-session-brief.md, code-verification.md, phase-roadmap.md
|
- `CLAUDE-nextsteps.md` — resume guide
|
||||||
- Updated QUICK-REF.md directory listing
|
- `docs/SESSION-HANDOFF.md` — this file
|
||||||
|
- `.claude/` config files, `.mcp.json`
|
||||||
|
- Various screenshot PNGs (can be cleaned up)
|
||||||
|
- Deleted docs: `CHECKPOINT.md`, `MASTER-SESSION-PROMPT.md`, `regression-checklist.md`
|
||||||
|
|
||||||
### Phase 4-6 Research — NOT COMMITTED (findings below)
|
### Accesslint Contrast Audit Results
|
||||||
Deep exploration of remaining token consolidation + sidebar UX issues. Ready to brainstorm into design.
|
All sidebar text passes WCAG AA across all 3 themes:
|
||||||
|
| Theme | Weakest Pair | Ratio |
|
||||||
|
|-------|-------------|-------|
|
||||||
|
| Dark | muted on card | 6.64:1 |
|
||||||
|
| Light | muted on sidebar-body | 6.54:1 |
|
||||||
|
| Glass | muted on card | 8.73:1 |
|
||||||
|
|
||||||
## Research Findings: Remaining Token Consolidation (Phases 4-6)
|
## What's Next
|
||||||
|
|
||||||
### Phase 4: Icon/Button Sizing (~12 locations)
|
### Priority 1: Google Fonts (approved, max 3)
|
||||||
Hardcoded icon/button sizes scattered across components.css:
|
- Google Fonts are on the table — user approved up to 3 (prefer), 4 hard cap
|
||||||
- 14px (export icon), 15px (sidebar icon), 16px (nudge nav), 18px (addon checkbox)
|
- Currently using: Poppins (hero numbers), DM Mono (values/mono), system sans-serif (body)
|
||||||
- 20px (HST toggle), 22px (M365 icon), 28px (mobile pill), 34px (chevron/toggle buttons)
|
- Explore: clean sans-serif for body/labels to improve readability at small sizes
|
||||||
- **Missing tokens:** `--icon-size-xs` through `--icon-size-xl`, `--control-icon-size`
|
- Consider: Inter, IBM Plex Sans, Source Sans 3, or similar
|
||||||
|
|
||||||
### Phase 5: Typography Scale (~36 locations)
|
### Priority 2: GUI Polish List A
|
||||||
**Letter-spacing (26+ occurrences, no tokens):**
|
- Contract Term pills — more vertical padding on unselected
|
||||||
- 0.02em (price values), 0.04em (meta labels), 0.06em (waive labels)
|
- Section card collapsed headers — slightly tight vertically
|
||||||
- 0.07em (badges/floor notes), 0.08em (buttons/badges), 0.1em (section titles)
|
- "MANAGED IT SERVICES (SECTIONS I, II, III)" label — low contrast
|
||||||
- 0.12em (labels/headings), 0.14em (nav), 0.18em (uppercase kickers), -0.02em (MRR hero)
|
- Insight carousel text — more line-height
|
||||||
- **Missing tokens:** `--text-spacing-tight/compact/normal/loose` etc.
|
- "QUOTE NOTES" label spacing — inconsistent with other section labels
|
||||||
|
|
||||||
**Font-size (hardcoded, not using tokens):**
|
### Priority 3: Typography Token Pass
|
||||||
- 8px (best value badge), 10px (mobile pill label), 12px (various labels)
|
- Tokenize remaining hardcoded letter-spacing values (~20 locations)
|
||||||
- 13px (sublabels), 14px (addon name, fee table), 15px (section meta)
|
- Tokenize hardcoded font-sizes not yet on tokens
|
||||||
- 16px (input label), 18px (sidebar hero responsive), 20px (modal icon)
|
- Move sidebar typography tokens from components.css to tokens.css
|
||||||
|
- Phase 4: icon/button sizing (~12 hardcoded values)
|
||||||
|
- Phase 6: responsive breakpoint font-sizes (optional)
|
||||||
|
|
||||||
**Sidebar typography tokens defined in components.css (should be in tokens.css):**
|
### Priority 4: Sections IV-VI Activation
|
||||||
- `--sidebar-copy-size`, `--sidebar-copy-line`, `--sidebar-note-size`, `--sidebar-mono-size`, `--sidebar-mrr-size` (components.css lines 1019-1023)
|
### Priority 5: Mobile Panel UX Enhancements
|
||||||
|
|
||||||
### Phase 6: Responsive Breakpoints (optional)
|
## Key References
|
||||||
Hardcoded responsive font-sizes in @media rules:
|
- Token file: `SVS-MSP-Calculator-tokens.css`
|
||||||
- 1.375rem, 1.125rem, 1.1rem (mobile text), 2.25rem/1.875rem (sidebar MRR at breakpoints)
|
- Sidebar styles: `SVS-MSP-Calculator-components.css`
|
||||||
- Could benefit from `--text-size-responsive-*` tokens
|
- Theme overrides: `SVS-MSP-Calculator-light.css`, `SVS-MSP-Calculator-glass.css`
|
||||||
|
- Tests: `node tests/test-quote-engine.js` — 254/254
|
||||||
## Research Findings: Sidebar UX Issues
|
|
||||||
|
|
||||||
### Divider/Line Inconsistency Across Themes
|
|
||||||
Sidebar divider tokens exist but values differ per theme:
|
|
||||||
- **Dark:** `color-mix(in srgb, var(--border) 88%, transparent)` — dashed
|
|
||||||
- **Light:** `color-mix(in srgb, var(--border) 70%, transparent)` — lighter
|
|
||||||
- **Glass:** `rgba(143, 183, 221, 0.12)` — very light
|
|
||||||
- **Risk:** Extra/mismatched horizontal lines visible on some themes. Needs Playwright visual audit.
|
|
||||||
|
|
||||||
### Price Value Letter-Spacing
|
|
||||||
- Current: `letter-spacing: 0.02em` on `.sidebar-line .val` (components.css ~line 1211)
|
|
||||||
- User feedback: prices ($x.xx) look "bunched" — needs more breathing room
|
|
||||||
- `font-variant-numeric: tabular-nums` IS applied globally (good)
|
|
||||||
- Consider bumping to 0.04em-0.06em and tokenizing as `--text-spacing-money`
|
|
||||||
|
|
||||||
### Sidebar Line Variants
|
|
||||||
| Element | Style | Notes |
|
|
||||||
|---------|-------|-------|
|
|
||||||
| `.sidebar-line` | dashed bottom border | Uses `--sidebar-line-rule-style` |
|
|
||||||
| `.sidebar-line-opportunity` | dotted | No token for line-style |
|
|
||||||
| `.sidebar-line-discount` | dashed, opacity 0.8 | Hardcoded opacity |
|
|
||||||
| `.sidebar-line-hst` | double top border | Uses token |
|
|
||||||
| `.sidebar-line-total` | solid top border | Uses `--sidebar-total-rule` (good) |
|
|
||||||
|
|
||||||
## Files Modified (this session)
|
|
||||||
|
|
||||||
| File | Change |
|
|
||||||
|------|--------|
|
|
||||||
| 7 CSS files | Token consolidation (committed) |
|
|
||||||
| 11 docs files | Deleted (committed) |
|
|
||||||
| `docs/QUICK-REF.md` | Updated directory listing (committed) |
|
|
||||||
| `docs/SESSION-HANDOFF.md` | Updated (committed) |
|
|
||||||
|
|
||||||
## Test Status
|
## Test Status
|
||||||
```
|
```
|
||||||
@@ -88,62 +69,18 @@ Sidebar divider tokens exist but values differ per theme:
|
|||||||
node tests/test-quote-engine.js
|
node tests/test-quote-engine.js
|
||||||
```
|
```
|
||||||
|
|
||||||
## What's Next
|
|
||||||
|
|
||||||
**Priority 1 (next session):** Sidebar UX fine-tune + Phase 4-6 token consolidation
|
|
||||||
- Brainstorm into design (was about to start — visual companion offered, user paused)
|
|
||||||
- Use `superpowers:brainstorming` → design spec → `superpowers:writing-plans`
|
|
||||||
- Dispatch UI/UX designer (Opus + ui-ux-pro-max) for sidebar line/spacing decisions
|
|
||||||
- Use Playwright MCP for visual comparison across themes
|
|
||||||
|
|
||||||
**Priority 2:** Sections IV-VI activation
|
|
||||||
**Priority 3:** Mobile panel UX enhancements
|
|
||||||
**Priority 4:** Beta readiness audit
|
|
||||||
|
|
||||||
## Continuation Prompt
|
## Continuation Prompt
|
||||||
|
|
||||||
```
|
```
|
||||||
Read docs/SESSION-HANDOFF.md then docs/QUICK-REF.md.
|
Read docs/SESSION-HANDOFF.md and CLAUDE-nextsteps.md.
|
||||||
3 themes: Dark, Light, Glass.
|
3 themes: Dark, Light, Glass.
|
||||||
254 tests must pass: node tests/test-quote-engine.js
|
254 tests must pass: node tests/test-quote-engine.js
|
||||||
|
|
||||||
## Resume Point: Sidebar UX Fine-Tune + Token Consolidation (Phases 4-6)
|
## Resume Point: Google Fonts + GUI Polish
|
||||||
|
|
||||||
Research is DONE (see SESSION-HANDOFF.md "Research Findings" sections).
|
Google Fonts approved (max 3). Currently: Poppins, DM Mono, system sans-serif.
|
||||||
Design has NOT started yet. Pick up at brainstorming.
|
Pick a body/label font, then continue with polish list A items.
|
||||||
|
|
||||||
### Two workstreams to combine into one design:
|
Use Playwright MCP for visual verification across all 3 themes.
|
||||||
|
Use accesslint for contrast checking on any color changes.
|
||||||
**1. Sidebar UX fine-tune (user-reported issues):**
|
|
||||||
- Extra horizontal lines in sidebar that don't match across Dark/Light/Glass themes
|
|
||||||
- Price values ($x.xx) look "bunched" — need more letter-spacing
|
|
||||||
- Divider opacity/color differs per theme (Dark 88%, Light 70%, Glass 12% rgba)
|
|
||||||
- Use Playwright MCP to screenshot sidebar across all 3 themes for visual comparison
|
|
||||||
- Dispatch UI/UX designer (Opus + ui-ux-pro-max skill) for design decisions
|
|
||||||
|
|
||||||
**2. Token consolidation Phases 4-6:**
|
|
||||||
- Phase 4: icon/button sizing (~12 hardcoded values → --icon-size-* tokens)
|
|
||||||
- Phase 5: typography scale (~36 locations — letter-spacing + font-size + move sidebar tokens to tokens.css)
|
|
||||||
- Phase 6: responsive breakpoints (optional — hardcoded rem values in @media)
|
|
||||||
|
|
||||||
### Action: Start with superpowers:brainstorming
|
|
||||||
- Project context: ALREADY EXPLORED (skip step 1 of brainstorming checklist)
|
|
||||||
- Offer visual companion (Playwright) — user was offered, hadn't accepted yet
|
|
||||||
- Then clarifying questions → approaches → design → spec → plan
|
|
||||||
|
|
||||||
## Key References
|
|
||||||
- Token file: SVS-MSP-Calculator-tokens.css
|
|
||||||
- Sidebar styles: SVS-MSP-Calculator-components.css (lines 1019-1023 sidebar tokens, 1183-1250 sidebar lines)
|
|
||||||
- Theme overrides: SVS-MSP-Calculator-light.css (lines 122-123), SVS-MSP-Calculator-glass.css (lines 194-195)
|
|
||||||
|
|
||||||
## Plugins Available — USE THEM
|
|
||||||
- **superpowers** — planning, subagent-driven development, parallel agents, TDD, debugging
|
|
||||||
- **ui-ux-pro-max** — design intelligence (palettes, typography, styles)
|
|
||||||
- **playwright** — visual verification in real browser
|
|
||||||
- **frontend-design**, **code-review**, **code-simplifier**
|
|
||||||
|
|
||||||
## Clear up DOCS folder
|
|
||||||
- clean up docs, remove old phases no longer under considertation, old instructions that no longer apply
|
|
||||||
|
|
||||||
Budget: stay under 60% context. Checkpoint before ending.
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,104 +0,0 @@
|
|||||||
# Regression Checklist
|
|
||||||
|
|
||||||
Use this when a change could affect runtime behavior. It is not required reading for every session.
|
|
||||||
|
|
||||||
## Automated tests (run first)
|
|
||||||
|
|
||||||
```
|
|
||||||
node svsmspcalc/tests/test-quote-engine.js
|
|
||||||
```
|
|
||||||
|
|
||||||
This covers all core quote math, admin fee logic, discounts, HST, VoIP, ZT, and MRR integrity. If all 88 tests pass, the items marked **[AUTO]** below are verified.
|
|
||||||
|
|
||||||
## Core quote math
|
|
||||||
|
|
||||||
- **[AUTO]** Verify M365 Included and BYOL base rates
|
|
||||||
- **[AUTO]** Verify each user add-on changes MRR correctly
|
|
||||||
- **[AUTO]** Verify endpoint count affects endpoint pricing
|
|
||||||
- **[AUTO]** Verify server count affects server pricing
|
|
||||||
- **[AUTO]** Verify Zero Trust Networking seat and HaaS counts affect MRR correctly
|
|
||||||
- **[AUTO]** Verify VoIP tier changes pricing correctly
|
|
||||||
- **[AUTO]** Verify desk phone and eFax add-ons change pricing correctly
|
|
||||||
|
|
||||||
## Site admin fee
|
|
||||||
|
|
||||||
- **[AUTO]** Verify admin fee floor at low subtotal
|
|
||||||
- **[AUTO]** Verify admin fee reduces as base subtotal rises
|
|
||||||
- **[AUTO]** Verify admin fee stops reducing at the configured floor
|
|
||||||
- **[AUTO]** Verify Zero Trust supplement appears when Zero Trust is active
|
|
||||||
- **[AUTO]** Verify 1Password admin surcharge appears when 1Password is selected
|
|
||||||
- **[AUTO]** Verify admin fee waive excludes fee from MRR
|
|
||||||
- [MANUAL] Verify admin fee waive state updates all relevant displays
|
|
||||||
|
|
||||||
## Contract term and onboarding
|
|
||||||
|
|
||||||
- **[AUTO]** Verify month-to-month has no discount
|
|
||||||
- **[AUTO]** Verify 12-month applies 3% discount
|
|
||||||
- **[AUTO]** Verify 24-month applies 5% discount
|
|
||||||
- [MANUAL] Verify 12-month auto-waives onboarding
|
|
||||||
- [MANUAL] Verify 24-month auto-waives onboarding
|
|
||||||
- [MANUAL] Verify switching back to month-to-month restores editable onboarding behavior
|
|
||||||
- [MANUAL] Verify manual onboarding override persists until reset by waiver logic
|
|
||||||
|
|
||||||
## Tax and totals
|
|
||||||
|
|
||||||
- **[AUTO]** Verify HST toggle applies 13% to discounted MRR
|
|
||||||
- **[AUTO]** Verify HST disabled = $0
|
|
||||||
- **[AUTO]** Verify HST applied after contract discount (not before)
|
|
||||||
- **[AUTO]** Verify annual projection matches effective MRR * 12
|
|
||||||
- **[AUTO]** Verify per-user effective cost is 0 when users = 0
|
|
||||||
|
|
||||||
## Sidebar and mobile parity
|
|
||||||
|
|
||||||
- [MANUAL] Verify desktop sidebar values match configured quote
|
|
||||||
- [MANUAL] Verify mobile pill MRR matches headline MRR
|
|
||||||
- [MANUAL] Verify mobile panel content matches desktop sidebar
|
|
||||||
- [MANUAL] Verify mobile HST toggle syncs back to desktop state
|
|
||||||
- [MANUAL] Verify nudge banner content and state match on desktop and mobile
|
|
||||||
|
|
||||||
## Persistence and reset
|
|
||||||
|
|
||||||
- [MANUAL] Verify quote state restores after reload
|
|
||||||
- [MANUAL] Verify theme preference restores after reload
|
|
||||||
- [MANUAL] Verify quote reference persists within the intended window
|
|
||||||
- [MANUAL] Verify reset clears quote state
|
|
||||||
- [MANUAL] Verify reset preserves theme preference
|
|
||||||
|
|
||||||
## Export and print
|
|
||||||
|
|
||||||
- [MANUAL] Verify Print / Save PDF opens the formatted print view
|
|
||||||
- [MANUAL] Verify print respects user's HST toggle state
|
|
||||||
- [MANUAL] Verify print view uses the expected totals, discount, onboarding, and HST states
|
|
||||||
- [MANUAL] Verify JSON export downloads a file
|
|
||||||
- [MANUAL] Verify JSON export copies content to clipboard when supported
|
|
||||||
- [MANUAL] Verify JSON payload matches the visible quote configuration
|
|
||||||
- [MANUAL] Verify JSON export includes `"version": "1.0"` field
|
|
||||||
|
|
||||||
## Section header layout
|
|
||||||
|
|
||||||
- [MANUAL] Verify all 6 sections show numeral + title + chevron on row 1
|
|
||||||
- [MANUAL] Verify controls row (stepper + badge + price) on row 2
|
|
||||||
- [MANUAL] Verify subtitle appears on row 3 only when expanded
|
|
||||||
- [MANUAL] Verify small-screen stacking at ≤600px / container ≤520px
|
|
||||||
- [MANUAL] Verify all elements span full width when stacked
|
|
||||||
|
|
||||||
## UX interactions (Phase 3)
|
|
||||||
|
|
||||||
- [MANUAL] Verify theme switch transitions smoothly (no flash)
|
|
||||||
- [MANUAL] Verify nudge banner crossfades on rotation and manual nav
|
|
||||||
- [MANUAL] Verify section summary badge fades in on collapse
|
|
||||||
- [MANUAL] Verify addon row shows subtle pulse on toggle
|
|
||||||
- [MANUAL] Verify touch targets ≥44px at ≤600px (close btn, nudge nav, section toggles)
|
|
||||||
- [MANUAL] Verify mobile panel traps focus (Tab cycles within panel)
|
|
||||||
- [MANUAL] Verify focus returns to pill on panel close
|
|
||||||
- [MANUAL] Verify safe-area insets respected on notch phones (pill position, panel padding)
|
|
||||||
|
|
||||||
## Accessibility (Phase 5)
|
|
||||||
|
|
||||||
- [MANUAL] Verify `aria-expanded` toggles on section headers when opening/closing
|
|
||||||
- [MANUAL] Verify `aria-expanded` toggles on collapsible headers when opening/closing
|
|
||||||
- [MANUAL] Verify reset modal traps focus (Tab cycles within modal, cannot reach background)
|
|
||||||
- [MANUAL] Verify stepper buttons announce descriptive labels via screen reader
|
|
||||||
- [MANUAL] Verify Glass theme scrolls smoothly on mobile (no jank from fixed backgrounds)
|
|
||||||
- [MANUAL] Verify mobile panel gets full sync on open (client name, discount label visible)
|
|
||||||
- [MANUAL] Verify mobile sync skips on desktop when panel is closed (performance)
|
|
||||||
BIN
glass-theme-full.png
Normal file
|
After Width: | Height: | Size: 594 KiB |
BIN
light-theme-full.png
Normal file
|
After Width: | Height: | Size: 270 KiB |
BIN
sidebar-dark-after.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
sidebar-dark.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
sidebar-glass-after.png
Normal file
|
After Width: | Height: | Size: 218 KiB |
BIN
sidebar-glass.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
sidebar-light-after.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
sidebar-light.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
sidebar-zones-dark.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
sidebar-zones-detail.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
this-is-the-area-with-extra-lines.png
Normal file
|
After Width: | Height: | Size: 29 KiB |