Add v2 rewrite: monorepo with desktop and web apps, shared packages, docs, and wireframes

This commit is contained in:
2026-05-14 13:13:21 -04:00
parent 6d7eed9230
commit 379c07b50c
49 changed files with 11607 additions and 0 deletions

9
.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
node_modules/
dist/
dist-electron/
apps/*/dist/
apps/*/dist-electron/
.DS_Store
*.log
*.local
*.tsbuildinfo

View File

@@ -0,0 +1,25 @@
- generic [ref=e3]:
- banner [ref=e4]:
- button "MosaicIQ" [ref=e5] [cursor=pointer]
- navigation "Screens" [ref=e6]:
- button "Home" [ref=e7]
- button "Workspace" [ref=e8]
- button "Model" [ref=e9]
- button "Memo" [ref=e10]
- button "Agents" [ref=e11]
- textbox "Search companies, files, commands..." [ref=e12]
- button "Settings" [ref=e13]: Gear
- button "JD" [ref=e14]
- main [ref=e15]:
- paragraph [ref=e16]: RPC Error
- heading "Electron preload bridge is unavailable." [level=1] [ref=e17]
- contentinfo [ref=e18]:
- generic [ref=e19]:
- button "<" [disabled] [ref=e20]
- strong [ref=e22]: No active agents
- generic [ref=e23]: Start a research pipeline from the Agents screen
- generic [ref=e24]: 0 / 0
- button ">" [disabled] [ref=e25]
- generic [ref=e26]:
- text: "> Ask an agent"
- generic [ref=e27]: Cmd K

View File

@@ -0,0 +1,223 @@
- generic [ref=e3]:
- banner [ref=e4]:
- button "MosaicIQ" [ref=e5] [cursor=pointer]
- navigation "Screens" [ref=e6]:
- button "Home" [ref=e7]
- button "Workspace" [ref=e8]
- button "Model" [ref=e9]
- button "Memo" [ref=e10]
- button "Agents" [ref=e11]
- textbox "Search companies, files, commands..." [ref=e12]
- button "Settings" [ref=e13]: Gear
- button "JD" [ref=e14]
- main [ref=e15]:
- generic [ref=e16]:
- generic [ref=e17]:
- generic [ref=e18]:
- paragraph [ref=e19]: Morning Briefing
- heading "Good morning, JD" [level=1] [ref=e20]
- button "Run Full Research" [ref=e21]
- generic [ref=e22]:
- generic [ref=e23]:
- paragraph [ref=e24]: Holdings
- generic [ref=e25]:
- button "COST $921.40 +1.2% 32% ..." [ref=e26]:
- generic [ref=e27]: COST
- generic [ref=e28]: $921.40
- generic [ref=e29]: +1.2%
- generic [ref=e30]: 32%
- generic [ref=e31]: ...
- button "AMZN $186.50 +0.8% 28% ..." [ref=e32]:
- generic [ref=e33]: AMZN
- generic [ref=e34]: $186.50
- generic [ref=e35]: +0.8%
- generic [ref=e36]: 28%
- generic [ref=e37]: ...
- button "WMT $168.30 -0.3% 22% ..." [ref=e38]:
- generic [ref=e39]: WMT
- generic [ref=e40]: $168.30
- generic [ref=e41]: "-0.3%"
- generic [ref=e42]: 22%
- generic [ref=e43]: ...
- button "TGT $142.80 -1.1% 18% ..." [ref=e44]:
- generic [ref=e45]: TGT
- generic [ref=e46]: $142.80
- generic [ref=e47]: "-1.1%"
- generic [ref=e48]: 18%
- generic [ref=e49]: ...
- button "BJ $84.20 +0.4% 8% ..." [ref=e50]:
- generic [ref=e51]: BJ
- generic [ref=e52]: $84.20
- generic [ref=e53]: +0.4%
- generic [ref=e54]: 8%
- generic [ref=e55]: ...
- button "KR $62.10 -0.6% 5% ..." [ref=e56]:
- generic [ref=e57]: KR
- generic [ref=e58]: $62.10
- generic [ref=e59]: "-0.6%"
- generic [ref=e60]: 5%
- generic [ref=e61]: ...
- button "DG $78.50 +1.5% 4% ..." [ref=e62]:
- generic [ref=e63]: DG
- generic [ref=e64]: $78.50
- generic [ref=e65]: +1.5%
- generic [ref=e66]: 4%
- generic [ref=e67]: ...
- generic [ref=e68]:
- paragraph [ref=e69]: Portfolio Performance (6M)
- img [ref=e70]
- generic [ref=e74]:
- generic [ref=e75]: Nov
- generic [ref=e76]: Dec
- generic [ref=e77]: Jan
- generic [ref=e78]: Feb
- generic [ref=e79]: Mar
- generic [ref=e80]: Apr
- generic [ref=e81]:
- generic [ref=e82]:
- generic [ref=e83]: Total Value
- strong [ref=e84]: $2.41M
- generic [ref=e85]:
- generic [ref=e86]: Day P&L
- strong [ref=e87]: +$12,340
- generic [ref=e88]:
- generic [ref=e89]: Holdings
- strong [ref=e90]: "7"
- generic [ref=e91]:
- generic [ref=e92]: Beta
- strong [ref=e93]: "0.68"
- generic [ref=e94]:
- generic [ref=e95]: Sharpe
- strong [ref=e96]: "1.42"
- generic [ref=e97]:
- generic [ref=e98]: Max DD
- strong [ref=e99]: "-4.1%"
- generic [ref=e100]:
- paragraph [ref=e101]: Exposure
- generic [ref=e102]:
- generic [ref=e103]:
- generic [ref=e104]:
- strong [ref=e105]: Consumer Staples
- generic [ref=e106]: 56%
- generic [ref=e107]:
- strong [ref=e108]: Consumer Disc.
- generic [ref=e109]: 26%
- generic [ref=e110]:
- generic [ref=e111]:
- strong [ref=e112]: Technology
- generic [ref=e113]: 10%
- generic [ref=e114]:
- strong [ref=e115]: Health Care
- generic [ref=e116]: 8%
- generic [ref=e117]:
- generic [ref=e118]:
- generic [ref=e119]: Value
- generic [ref=e122]: "+0.32"
- generic [ref=e123]:
- generic [ref=e124]: Size
- generic [ref=e127]: "-0.48"
- generic [ref=e128]:
- generic [ref=e129]: Momentum
- generic [ref=e132]: "+0.18"
- generic [ref=e133]:
- generic [ref=e134]: Quality
- generic [ref=e137]: "+0.41"
- generic [ref=e138]:
- generic [ref=e139]: Low Vol
- generic [ref=e142]: "+0.27"
- generic [ref=e143]:
- generic [ref=e144]:
- paragraph [ref=e145]: Earnings Calendar
- generic [ref=e146]:
- generic [ref=e147]: Tue May 12
- generic [ref=e148]: COST Q3 FY25
- generic [ref=e149]: BMO
- generic [ref=e150]: 3d
- generic [ref=e151]:
- generic [ref=e152]: Wed May 13
- generic [ref=e153]: WMT Q1 FY27
- generic [ref=e154]: BMO
- generic [ref=e155]: 4d
- generic [ref=e156]:
- generic [ref=e157]: Thu May 14
- generic [ref=e158]: AMZN Q2 FY26
- generic [ref=e159]: AMC
- generic [ref=e160]: 5d
- generic [ref=e161]:
- paragraph [ref=e162]: Watchlist Alerts
- generic [ref=e163]:
- generic [ref=e164]: 2h ago
- generic [ref=e165]: COST comp traffic beat internal threshold
- generic [ref=e166]: Thesis +
- generic [ref=e167]:
- generic [ref=e168]: 4h ago
- generic [ref=e169]: WMT price investment pressuring grocery basket
- generic [ref=e170]: Thesis -
- generic [ref=e171]:
- generic [ref=e172]: 1d ago
- generic [ref=e173]: New 10-Q filed for TGT
- generic [ref=e174]: Filing
- generic [ref=e175]:
- paragraph [ref=e176]: Recent Reports & Exports
- generic [ref=e177]:
- button "Investment Memo Membership Economics at Scale Draft - 2 reviews" [ref=e178]:
- generic [ref=e179]: Investment Memo
- strong [ref=e180]: Membership Economics at Scale
- generic [ref=e181]: Draft - 2 reviews
- button "Financial Model Revenue Build FY25-FY27 Base / Bull / Bear" [ref=e182]:
- generic [ref=e183]: Financial Model
- strong [ref=e184]: Revenue Build FY25-FY27
- generic [ref=e185]: Base / Bull / Bear
- button "Earnings Update Q2 FY25 Earnings Note Auto-generated - Mar 6" [ref=e186]:
- generic [ref=e187]: Earnings Update
- strong [ref=e188]: Q2 FY25 Earnings Note
- generic [ref=e189]: Auto-generated - Mar 6
- button "Peer Comparison Warehouse Club Margins COST vs WMT, TGT, BJ" [ref=e190]:
- generic [ref=e191]: Peer Comparison
- strong [ref=e192]: Warehouse Club Margins
- generic [ref=e193]: COST vs WMT, TGT, BJ
- button "Valuation DCF + Comps Analysis $720-$1,280 range" [ref=e194]:
- generic [ref=e195]: Valuation
- strong [ref=e196]: DCF + Comps Analysis
- generic [ref=e197]: $720-$1,280 range
- button "Risk Register Risk & Mitigant Framework 8 risks catalogued" [ref=e198]:
- generic [ref=e199]: Risk Register
- strong [ref=e200]: Risk & Mitigant Framework
- generic [ref=e201]: 8 risks catalogued
- generic [ref=e202]:
- paragraph [ref=e203]: Pending Reviews
- generic [ref=e204]:
- generic [ref=e205]: Memo
- generic [ref=e206]: Variant perception needs consensus margin citation
- generic [ref=e207]: 2 comments
- generic [ref=e208]:
- generic [ref=e209]: Model
- generic [ref=e210]: FY26 warehouse openings assumption flagged
- generic [ref=e211]: Review
- generic [ref=e212]:
- paragraph [ref=e213]: Agent Status
- generic [ref=e214]:
- generic [ref=e216]:
- strong [ref=e217]: SEC Filings Agent
- paragraph [ref=e218]: Extracting segment data from 10-K
- generic [ref=e219]: 45%
- generic [ref=e220]:
- generic [ref=e222]:
- strong [ref=e223]: Financial Modeling Agent
- paragraph [ref=e224]: Building revenue schedule
- generic [ref=e225]: 62%
- generic [ref=e226]:
- generic [ref=e228]:
- strong [ref=e229]: Earnings Call Agent
- paragraph [ref=e230]: Summarizing Q2 FY25 call
- generic [ref=e231]: 78%
- contentinfo [ref=e232]:
- generic [ref=e233]:
- button "<" [disabled] [ref=e234]
- strong [ref=e236]: SEC Filings Agent
- generic [ref=e237]: Extracting segment data from 10-K
- generic [ref=e238]: 1 / 3
- button ">" [ref=e239]
- generic [ref=e240]:
- text: "> Ask an agent"
- generic [ref=e241]: Cmd K

View File

@@ -0,0 +1,223 @@
- generic [ref=e3]:
- banner [ref=e4]:
- button "MosaicIQ" [ref=e5] [cursor=pointer]
- navigation "Screens" [ref=e6]:
- button "Home" [ref=e7]
- button "Workspace" [ref=e8]
- button "Model" [ref=e9]
- button "Memo" [ref=e10]
- button "Agents" [ref=e11]
- textbox "Search companies, files, commands..." [ref=e12]
- button "Settings" [ref=e13]: ⚙
- button "JD" [ref=e14]
- main [ref=e15]:
- generic [ref=e16]:
- generic [ref=e17]:
- generic [ref=e18]:
- paragraph [ref=e19]: Morning Briefing
- heading "Good morning, JD" [level=1] [ref=e20]
- button "Run Full Research" [ref=e21]
- generic [ref=e22]:
- generic [ref=e23]:
- paragraph [ref=e24]: Holdings
- generic [ref=e25]:
- button "COST $921.40 +1.2% 32% ..." [ref=e26]:
- generic [ref=e27]: COST
- generic [ref=e28]: $921.40
- generic [ref=e29]: +1.2%
- generic [ref=e30]: 32%
- generic [ref=e31]: ...
- button "AMZN $186.50 +0.8% 28% ..." [ref=e32]:
- generic [ref=e33]: AMZN
- generic [ref=e34]: $186.50
- generic [ref=e35]: +0.8%
- generic [ref=e36]: 28%
- generic [ref=e37]: ...
- button "WMT $168.30 -0.3% 22% ..." [ref=e38]:
- generic [ref=e39]: WMT
- generic [ref=e40]: $168.30
- generic [ref=e41]: "-0.3%"
- generic [ref=e42]: 22%
- generic [ref=e43]: ...
- button "TGT $142.80 -1.1% 18% ..." [ref=e44]:
- generic [ref=e45]: TGT
- generic [ref=e46]: $142.80
- generic [ref=e47]: "-1.1%"
- generic [ref=e48]: 18%
- generic [ref=e49]: ...
- button "BJ $84.20 +0.4% 8% ..." [ref=e50]:
- generic [ref=e51]: BJ
- generic [ref=e52]: $84.20
- generic [ref=e53]: +0.4%
- generic [ref=e54]: 8%
- generic [ref=e55]: ...
- button "KR $62.10 -0.6% 5% ..." [ref=e56]:
- generic [ref=e57]: KR
- generic [ref=e58]: $62.10
- generic [ref=e59]: "-0.6%"
- generic [ref=e60]: 5%
- generic [ref=e61]: ...
- button "DG $78.50 +1.5% 4% ..." [ref=e62]:
- generic [ref=e63]: DG
- generic [ref=e64]: $78.50
- generic [ref=e65]: +1.5%
- generic [ref=e66]: 4%
- generic [ref=e67]: ...
- generic [ref=e68]:
- paragraph [ref=e69]: Portfolio Performance (6M)
- img [ref=e70]
- generic [ref=e74]:
- generic [ref=e75]: Nov
- generic [ref=e76]: Dec
- generic [ref=e77]: Jan
- generic [ref=e78]: Feb
- generic [ref=e79]: Mar
- generic [ref=e80]: Apr
- generic [ref=e81]:
- generic [ref=e82]:
- generic [ref=e83]: Total Value
- strong [ref=e84]: $2.41M
- generic [ref=e85]:
- generic [ref=e86]: Day P&L
- strong [ref=e87]: +$12,340
- generic [ref=e88]:
- generic [ref=e89]: Holdings
- strong [ref=e90]: "7"
- generic [ref=e91]:
- generic [ref=e92]: Beta
- strong [ref=e93]: "0.68"
- generic [ref=e94]:
- generic [ref=e95]: Sharpe
- strong [ref=e96]: "1.42"
- generic [ref=e97]:
- generic [ref=e98]: Max DD
- strong [ref=e99]: "-4.1%"
- generic [ref=e100]:
- paragraph [ref=e101]: Exposure
- generic [ref=e102]:
- generic [ref=e103]:
- generic [ref=e104]:
- strong [ref=e105]: Consumer Staples
- generic [ref=e106]: 56%
- generic [ref=e107]:
- strong [ref=e108]: Consumer Disc.
- generic [ref=e109]: 26%
- generic [ref=e110]:
- generic [ref=e111]:
- strong [ref=e112]: Technology
- generic [ref=e113]: 10%
- generic [ref=e114]:
- strong [ref=e115]: Health Care
- generic [ref=e116]: 8%
- generic [ref=e117]:
- generic [ref=e118]:
- generic [ref=e119]: Value
- generic [ref=e122]: "+0.32"
- generic [ref=e123]:
- generic [ref=e124]: Size
- generic [ref=e127]: "-0.48"
- generic [ref=e128]:
- generic [ref=e129]: Momentum
- generic [ref=e132]: "+0.18"
- generic [ref=e133]:
- generic [ref=e134]: Quality
- generic [ref=e137]: "+0.41"
- generic [ref=e138]:
- generic [ref=e139]: Low Vol
- generic [ref=e142]: "+0.27"
- generic [ref=e143]:
- generic [ref=e144]:
- paragraph [ref=e145]: Earnings Calendar
- generic [ref=e146]:
- generic [ref=e147]: Tue May 12
- generic [ref=e148]: COST Q3 FY25
- generic [ref=e149]: BMO
- generic [ref=e150]: 3d
- generic [ref=e151]:
- generic [ref=e152]: Wed May 13
- generic [ref=e153]: WMT Q1 FY27
- generic [ref=e154]: BMO
- generic [ref=e155]: 4d
- generic [ref=e156]:
- generic [ref=e157]: Thu May 14
- generic [ref=e158]: AMZN Q2 FY26
- generic [ref=e159]: AMC
- generic [ref=e160]: 5d
- generic [ref=e161]:
- paragraph [ref=e162]: Watchlist Alerts
- generic [ref=e163]:
- generic [ref=e164]: 2h ago
- generic [ref=e165]: COST comp traffic beat internal threshold
- generic [ref=e166]: Thesis +
- generic [ref=e167]:
- generic [ref=e168]: 4h ago
- generic [ref=e169]: WMT price investment pressuring grocery basket
- generic [ref=e170]: Thesis -
- generic [ref=e171]:
- generic [ref=e172]: 1d ago
- generic [ref=e173]: New 10-Q filed for TGT
- generic [ref=e174]: Filing
- generic [ref=e175]:
- paragraph [ref=e176]: Recent Reports & Exports
- generic [ref=e177]:
- button "Investment Memo Membership Economics at Scale Draft - 2 reviews" [ref=e178]:
- generic [ref=e179]: Investment Memo
- strong [ref=e180]: Membership Economics at Scale
- generic [ref=e181]: Draft - 2 reviews
- button "Financial Model Revenue Build FY25-FY27 Base / Bull / Bear" [ref=e182]:
- generic [ref=e183]: Financial Model
- strong [ref=e184]: Revenue Build FY25-FY27
- generic [ref=e185]: Base / Bull / Bear
- button "Earnings Update Q2 FY25 Earnings Note Auto-generated - Mar 6" [ref=e186]:
- generic [ref=e187]: Earnings Update
- strong [ref=e188]: Q2 FY25 Earnings Note
- generic [ref=e189]: Auto-generated - Mar 6
- button "Peer Comparison Warehouse Club Margins COST vs WMT, TGT, BJ" [ref=e190]:
- generic [ref=e191]: Peer Comparison
- strong [ref=e192]: Warehouse Club Margins
- generic [ref=e193]: COST vs WMT, TGT, BJ
- button "Valuation DCF + Comps Analysis $720-$1,280 range" [ref=e194]:
- generic [ref=e195]: Valuation
- strong [ref=e196]: DCF + Comps Analysis
- generic [ref=e197]: $720-$1,280 range
- button "Risk Register Risk & Mitigant Framework 8 risks catalogued" [ref=e198]:
- generic [ref=e199]: Risk Register
- strong [ref=e200]: Risk & Mitigant Framework
- generic [ref=e201]: 8 risks catalogued
- generic [ref=e202]:
- paragraph [ref=e203]: Pending Reviews
- generic [ref=e204]:
- generic [ref=e205]: Memo
- generic [ref=e206]: Variant perception needs consensus margin citation
- generic [ref=e207]: 2 comments
- generic [ref=e208]:
- generic [ref=e209]: Model
- generic [ref=e210]: FY26 warehouse openings assumption flagged
- generic [ref=e211]: Review
- generic [ref=e212]:
- paragraph [ref=e213]: Agent Status
- generic [ref=e214]:
- generic [ref=e216]:
- strong [ref=e217]: SEC Filings Agent
- paragraph [ref=e218]: Extracting segment data from 10-K
- generic [ref=e219]: 45%
- generic [ref=e220]:
- generic [ref=e222]:
- strong [ref=e223]: Financial Modeling Agent
- paragraph [ref=e224]: Building revenue schedule
- generic [ref=e225]: 62%
- generic [ref=e226]:
- generic [ref=e228]:
- strong [ref=e229]: Earnings Call Agent
- paragraph [ref=e230]: Summarizing Q2 FY25 call
- generic [ref=e231]: 78%
- contentinfo [ref=e232]:
- generic [ref=e233]:
- button "<" [disabled] [ref=e234]
- strong [ref=e236]: SEC Filings Agent
- generic [ref=e237]: Extracting segment data from 10-K
- generic [ref=e238]: 1 / 3
- button ">" [ref=e239]
- generic [ref=e240]:
- text: "> Ask an agent"
- generic [ref=e241]: Cmd K

View File

@@ -0,0 +1,216 @@
- generic [ref=e3]:
- banner [ref=e4]:
- button "MosaicIQ" [ref=e5] [cursor=pointer]
- navigation "Screens" [ref=e6]:
- button "Home" [ref=e7]
- button "Workspace" [ref=e8]
- button "Model" [ref=e9]
- button "Memo" [ref=e10]
- button "Agents" [ref=e11]
- textbox "Search companies, files, commands..." [ref=e12]
- button "Settings" [ref=e13]: ⚙
- button "JD" [ref=e14]
- main [ref=e15]:
- generic [ref=e16]:
- generic [ref=e17]:
- generic [ref=e18]:
- paragraph [ref=e19]: Morning Briefing
- heading "Good morning, JD" [level=1] [ref=e20]
- button "Run Full Research" [ref=e21]
- generic [ref=e22]:
- generic [ref=e23]:
- paragraph [ref=e24]: Holdings
- generic [ref=e25]:
- button "COST $921.40 +1.2% 32%" [ref=e26]:
- generic [ref=e27]: COST
- generic [ref=e28]: $921.40
- generic [ref=e29]: +1.2%
- generic [ref=e30]: 32%
- button "AMZN $186.50 +0.8% 28%" [ref=e31]:
- generic [ref=e32]: AMZN
- generic [ref=e33]: $186.50
- generic [ref=e34]: +0.8%
- generic [ref=e35]: 28%
- button "WMT $168.30 -0.3% 22%" [ref=e36]:
- generic [ref=e37]: WMT
- generic [ref=e38]: $168.30
- generic [ref=e39]: "-0.3%"
- generic [ref=e40]: 22%
- button "TGT $142.80 -1.1% 18%" [ref=e41]:
- generic [ref=e42]: TGT
- generic [ref=e43]: $142.80
- generic [ref=e44]: "-1.1%"
- generic [ref=e45]: 18%
- button "BJ $84.20 +0.4% 8%" [ref=e46]:
- generic [ref=e47]: BJ
- generic [ref=e48]: $84.20
- generic [ref=e49]: +0.4%
- generic [ref=e50]: 8%
- button "KR $62.10 -0.6% 5%" [ref=e51]:
- generic [ref=e52]: KR
- generic [ref=e53]: $62.10
- generic [ref=e54]: "-0.6%"
- generic [ref=e55]: 5%
- button "DG $78.50 +1.5% 4%" [ref=e56]:
- generic [ref=e57]: DG
- generic [ref=e58]: $78.50
- generic [ref=e59]: +1.5%
- generic [ref=e60]: 4%
- generic [ref=e61]:
- paragraph [ref=e62]: Portfolio Performance (6M)
- img [ref=e63]
- generic [ref=e67]:
- generic [ref=e68]: Nov
- generic [ref=e69]: Dec
- generic [ref=e70]: Jan
- generic [ref=e71]: Feb
- generic [ref=e72]: Mar
- generic [ref=e73]: Apr
- generic [ref=e74]:
- generic [ref=e75]:
- generic [ref=e76]: Total Value
- strong [ref=e77]: $2.41M
- generic [ref=e78]:
- generic [ref=e79]: Day P&L
- strong [ref=e80]: +$12,340
- generic [ref=e81]:
- generic [ref=e82]: Holdings
- strong [ref=e83]: "7"
- generic [ref=e84]:
- generic [ref=e85]: Beta
- strong [ref=e86]: "0.68"
- generic [ref=e87]:
- generic [ref=e88]: Sharpe
- strong [ref=e89]: "1.42"
- generic [ref=e90]:
- generic [ref=e91]: Max DD
- strong [ref=e92]: "-4.1%"
- generic [ref=e93]:
- paragraph [ref=e94]: Exposure
- generic [ref=e95]:
- generic [ref=e96]:
- generic [ref=e97]:
- strong [ref=e98]: Consumer Staples
- generic [ref=e99]: 56%
- generic [ref=e100]:
- strong [ref=e101]: Consumer Disc.
- generic [ref=e102]: 26%
- generic [ref=e103]:
- generic [ref=e104]:
- strong [ref=e105]: Technology
- generic [ref=e106]: 10%
- generic [ref=e107]:
- strong [ref=e108]: Health Care
- generic [ref=e109]: 8%
- generic [ref=e110]:
- generic [ref=e111]:
- generic [ref=e112]: Value
- generic [ref=e115]: "+0.32"
- generic [ref=e116]:
- generic [ref=e117]: Size
- generic [ref=e120]: "-0.48"
- generic [ref=e121]:
- generic [ref=e122]: Momentum
- generic [ref=e125]: "+0.18"
- generic [ref=e126]:
- generic [ref=e127]: Quality
- generic [ref=e130]: "+0.41"
- generic [ref=e131]:
- generic [ref=e132]: Low Vol
- generic [ref=e135]: "+0.27"
- generic [ref=e136]:
- generic [ref=e137]:
- paragraph [ref=e138]: Earnings Calendar
- generic [ref=e139]:
- generic [ref=e140]: Tue May 12
- generic [ref=e141]: COST Q3 FY25
- generic [ref=e142]: BMO
- generic [ref=e143]: 3d
- generic [ref=e144]:
- generic [ref=e145]: Wed May 13
- generic [ref=e146]: WMT Q1 FY27
- generic [ref=e147]: BMO
- generic [ref=e148]: 4d
- generic [ref=e149]:
- generic [ref=e150]: Thu May 14
- generic [ref=e151]: AMZN Q2 FY26
- generic [ref=e152]: AMC
- generic [ref=e153]: 5d
- generic [ref=e154]:
- paragraph [ref=e155]: Watchlist Alerts
- generic [ref=e156]:
- generic [ref=e157]: 2h ago
- generic [ref=e158]: COST comp traffic beat internal threshold
- generic [ref=e159]: Thesis +
- generic [ref=e160]:
- generic [ref=e161]: 4h ago
- generic [ref=e162]: WMT price investment pressuring grocery basket
- generic [ref=e163]: Thesis -
- generic [ref=e164]:
- generic [ref=e165]: 1d ago
- generic [ref=e166]: New 10-Q filed for TGT
- generic [ref=e167]: Filing
- generic [ref=e168]:
- paragraph [ref=e169]: Recent Reports & Exports
- generic [ref=e170]:
- button "Investment Memo Membership Economics at Scale Draft - 2 reviews" [ref=e171]:
- generic [ref=e172]: Investment Memo
- strong [ref=e173]: Membership Economics at Scale
- generic [ref=e174]: Draft - 2 reviews
- button "Financial Model Revenue Build FY25-FY27 Base / Bull / Bear" [ref=e175]:
- generic [ref=e176]: Financial Model
- strong [ref=e177]: Revenue Build FY25-FY27
- generic [ref=e178]: Base / Bull / Bear
- button "Earnings Update Q2 FY25 Earnings Note Auto-generated - Mar 6" [ref=e179]:
- generic [ref=e180]: Earnings Update
- strong [ref=e181]: Q2 FY25 Earnings Note
- generic [ref=e182]: Auto-generated - Mar 6
- button "Peer Comparison Warehouse Club Margins COST vs WMT, TGT, BJ" [ref=e183]:
- generic [ref=e184]: Peer Comparison
- strong [ref=e185]: Warehouse Club Margins
- generic [ref=e186]: COST vs WMT, TGT, BJ
- button "Valuation DCF + Comps Analysis $720-$1,280 range" [ref=e187]:
- generic [ref=e188]: Valuation
- strong [ref=e189]: DCF + Comps Analysis
- generic [ref=e190]: $720-$1,280 range
- button "Risk Register Risk & Mitigant Framework 8 risks catalogued" [ref=e191]:
- generic [ref=e192]: Risk Register
- strong [ref=e193]: Risk & Mitigant Framework
- generic [ref=e194]: 8 risks catalogued
- generic [ref=e195]:
- paragraph [ref=e196]: Pending Reviews
- generic [ref=e197]:
- generic [ref=e198]: Memo
- generic [ref=e199]: Variant perception needs consensus margin citation
- generic [ref=e200]: 2 comments
- generic [ref=e201]:
- generic [ref=e202]: Model
- generic [ref=e203]: FY26 warehouse openings assumption flagged
- generic [ref=e204]: Review
- generic [ref=e205]:
- paragraph [ref=e206]: Agent Status
- generic [ref=e207]:
- generic [ref=e209]:
- strong [ref=e210]: SEC Filings Agent
- paragraph [ref=e211]: Extracting segment data from 10-K
- generic [ref=e212]: 45%
- generic [ref=e213]:
- generic [ref=e215]:
- strong [ref=e216]: Financial Modeling Agent
- paragraph [ref=e217]: Building revenue schedule
- generic [ref=e218]: 62%
- generic [ref=e219]:
- generic [ref=e221]:
- strong [ref=e222]: Earnings Call Agent
- paragraph [ref=e223]: Summarizing Q2 FY25 call
- generic [ref=e224]: 78%
- contentinfo [ref=e225]:
- generic [ref=e226]:
- button "<" [disabled] [ref=e227]
- strong [ref=e229]: SEC Filings Agent
- generic [ref=e230]: Extracting segment data from 10-K
- generic [ref=e231]: 1 / 3
- button ">" [ref=e232]
- generic [ref=e233]:
- text: "> Ask an agent"
- generic [ref=e234]: Cmd K

View File

@@ -0,0 +1,216 @@
- generic [ref=e3]:
- banner [ref=e4]:
- button "MosaicIQ" [ref=e5] [cursor=pointer]
- navigation "Screens" [ref=e6]:
- button "Home" [ref=e7]
- button "Workspace" [ref=e8]
- button "Model" [ref=e9]
- button "Memo" [ref=e10]
- button "Agents" [ref=e11]
- textbox "Search companies, files, commands..." [ref=e12]
- button "Settings" [ref=e13]: ⚙
- button "JD" [ref=e14]
- main [ref=e15]:
- generic [ref=e16]:
- generic [ref=e17]:
- generic [ref=e18]:
- paragraph [ref=e19]: Morning Briefing
- heading "Good morning, JD" [level=1] [ref=e20]
- button "Run Full Research" [ref=e21]
- generic [ref=e22]:
- generic [ref=e23]:
- paragraph [ref=e24]: Holdings
- generic [ref=e25]:
- button "COST $921.40 +1.2% 32%" [ref=e26]:
- generic [ref=e27]: COST
- generic [ref=e28]: $921.40
- generic [ref=e29]: +1.2%
- generic [ref=e30]: 32%
- button "AMZN $186.50 +0.8% 28%" [ref=e31]:
- generic [ref=e32]: AMZN
- generic [ref=e33]: $186.50
- generic [ref=e34]: +0.8%
- generic [ref=e35]: 28%
- button "WMT $168.30 -0.3% 22%" [ref=e36]:
- generic [ref=e37]: WMT
- generic [ref=e38]: $168.30
- generic [ref=e39]: "-0.3%"
- generic [ref=e40]: 22%
- button "TGT $142.80 -1.1% 18%" [ref=e41]:
- generic [ref=e42]: TGT
- generic [ref=e43]: $142.80
- generic [ref=e44]: "-1.1%"
- generic [ref=e45]: 18%
- button "BJ $84.20 +0.4% 8%" [ref=e46]:
- generic [ref=e47]: BJ
- generic [ref=e48]: $84.20
- generic [ref=e49]: +0.4%
- generic [ref=e50]: 8%
- button "KR $62.10 -0.6% 5%" [ref=e51]:
- generic [ref=e52]: KR
- generic [ref=e53]: $62.10
- generic [ref=e54]: "-0.6%"
- generic [ref=e55]: 5%
- button "DG $78.50 +1.5% 4%" [ref=e56]:
- generic [ref=e57]: DG
- generic [ref=e58]: $78.50
- generic [ref=e59]: +1.5%
- generic [ref=e60]: 4%
- generic [ref=e61]:
- paragraph [ref=e62]: Portfolio Performance (6M)
- img [ref=e63]
- generic [ref=e67]:
- generic [ref=e68]: Nov
- generic [ref=e69]: Dec
- generic [ref=e70]: Jan
- generic [ref=e71]: Feb
- generic [ref=e72]: Mar
- generic [ref=e73]: Apr
- generic [ref=e74]:
- generic [ref=e75]:
- generic [ref=e76]: Total Value
- strong [ref=e77]: $2.41M
- generic [ref=e78]:
- generic [ref=e79]: Day P&L
- strong [ref=e80]: +$12,340
- generic [ref=e81]:
- generic [ref=e82]: Holdings
- strong [ref=e83]: "7"
- generic [ref=e84]:
- generic [ref=e85]: Beta
- strong [ref=e86]: "0.68"
- generic [ref=e87]:
- generic [ref=e88]: Sharpe
- strong [ref=e89]: "1.42"
- generic [ref=e90]:
- generic [ref=e91]: Max DD
- strong [ref=e92]: "-4.1%"
- generic [ref=e93]:
- paragraph [ref=e94]: Exposure
- generic [ref=e95]:
- generic [ref=e96]:
- generic [ref=e97]:
- strong [ref=e98]: Consumer Staples
- generic [ref=e99]: 56%
- generic [ref=e100]:
- strong [ref=e101]: Consumer Disc.
- generic [ref=e102]: 26%
- generic [ref=e103]:
- generic [ref=e104]:
- strong [ref=e105]: Technology
- generic [ref=e106]: 10%
- generic [ref=e107]:
- strong [ref=e108]: Health Care
- generic [ref=e109]: 8%
- generic [ref=e110]:
- generic [ref=e111]:
- generic [ref=e112]: Value
- generic [ref=e115]: "+0.32"
- generic [ref=e116]:
- generic [ref=e117]: Size
- generic [ref=e120]: "-0.48"
- generic [ref=e121]:
- generic [ref=e122]: Momentum
- generic [ref=e125]: "+0.18"
- generic [ref=e126]:
- generic [ref=e127]: Quality
- generic [ref=e130]: "+0.41"
- generic [ref=e131]:
- generic [ref=e132]: Low Vol
- generic [ref=e135]: "+0.27"
- generic [ref=e136]:
- generic [ref=e137]:
- paragraph [ref=e138]: Earnings Calendar
- generic [ref=e139]:
- generic [ref=e140]: Tue May 12
- generic [ref=e141]: COST Q3 FY25
- generic [ref=e142]: BMO
- generic [ref=e143]: 3d
- generic [ref=e144]:
- generic [ref=e145]: Wed May 13
- generic [ref=e146]: WMT Q1 FY27
- generic [ref=e147]: BMO
- generic [ref=e148]: 4d
- generic [ref=e149]:
- generic [ref=e150]: Thu May 14
- generic [ref=e151]: AMZN Q2 FY26
- generic [ref=e152]: AMC
- generic [ref=e153]: 5d
- generic [ref=e154]:
- paragraph [ref=e155]: Watchlist Alerts
- generic [ref=e156]:
- generic [ref=e157]: 2h ago
- generic [ref=e158]: COST comp traffic beat internal threshold
- generic [ref=e159]: Thesis +
- generic [ref=e160]:
- generic [ref=e161]: 4h ago
- generic [ref=e162]: WMT price investment pressuring grocery basket
- generic [ref=e163]: Thesis -
- generic [ref=e164]:
- generic [ref=e165]: 1d ago
- generic [ref=e166]: New 10-Q filed for TGT
- generic [ref=e167]: Filing
- generic [ref=e168]:
- paragraph [ref=e169]: Recent Reports & Exports
- generic [ref=e170]:
- button "Investment Memo Membership Economics at Scale Draft - 2 reviews" [ref=e171]:
- generic [ref=e172]: Investment Memo
- strong [ref=e173]: Membership Economics at Scale
- generic [ref=e174]: Draft - 2 reviews
- button "Financial Model Revenue Build FY25-FY27 Base / Bull / Bear" [ref=e175]:
- generic [ref=e176]: Financial Model
- strong [ref=e177]: Revenue Build FY25-FY27
- generic [ref=e178]: Base / Bull / Bear
- button "Earnings Update Q2 FY25 Earnings Note Auto-generated - Mar 6" [ref=e179]:
- generic [ref=e180]: Earnings Update
- strong [ref=e181]: Q2 FY25 Earnings Note
- generic [ref=e182]: Auto-generated - Mar 6
- button "Peer Comparison Warehouse Club Margins COST vs WMT, TGT, BJ" [ref=e183]:
- generic [ref=e184]: Peer Comparison
- strong [ref=e185]: Warehouse Club Margins
- generic [ref=e186]: COST vs WMT, TGT, BJ
- button "Valuation DCF + Comps Analysis $720-$1,280 range" [ref=e187]:
- generic [ref=e188]: Valuation
- strong [ref=e189]: DCF + Comps Analysis
- generic [ref=e190]: $720-$1,280 range
- button "Risk Register Risk & Mitigant Framework 8 risks catalogued" [ref=e191]:
- generic [ref=e192]: Risk Register
- strong [ref=e193]: Risk & Mitigant Framework
- generic [ref=e194]: 8 risks catalogued
- generic [ref=e195]:
- paragraph [ref=e196]: Pending Reviews
- generic [ref=e197]:
- generic [ref=e198]: Memo
- generic [ref=e199]: Variant perception needs consensus margin citation
- generic [ref=e200]: 2 comments
- generic [ref=e201]:
- generic [ref=e202]: Model
- generic [ref=e203]: FY26 warehouse openings assumption flagged
- generic [ref=e204]: Review
- generic [ref=e205]:
- paragraph [ref=e206]: Agent Status
- generic [ref=e207]:
- generic [ref=e209]:
- strong [ref=e210]: SEC Filings Agent
- paragraph [ref=e211]: Extracting segment data from 10-K
- generic [ref=e212]: 45%
- generic [ref=e213]:
- generic [ref=e215]:
- strong [ref=e216]: Financial Modeling Agent
- paragraph [ref=e217]: Building revenue schedule
- generic [ref=e218]: 62%
- generic [ref=e219]:
- generic [ref=e221]:
- strong [ref=e222]: Earnings Call Agent
- paragraph [ref=e223]: Summarizing Q2 FY25 call
- generic [ref=e224]: 78%
- contentinfo [ref=e225]:
- generic [ref=e226]:
- button "<" [disabled] [ref=e227]
- strong [ref=e229]: SEC Filings Agent
- generic [ref=e230]: Extracting segment data from 10-K
- generic [ref=e231]: 1 / 3
- button ">" [ref=e232]
- generic [ref=e233]:
- text: "> Ask an agent"
- generic [ref=e234]: Cmd K

View File

@@ -0,0 +1,216 @@
- generic [ref=e3]:
- banner [ref=e4]:
- button "MosaicIQ" [ref=e5] [cursor=pointer]
- navigation "Screens" [ref=e6]:
- button "Home" [ref=e7]
- button "Workspace" [ref=e8]
- button "Model" [ref=e9]
- button "Memo" [ref=e10]
- button "Agents" [ref=e11]
- textbox "Search companies, files, commands..." [ref=e12]
- button "Settings" [ref=e13]: ⚙
- button "JD" [ref=e14]
- main [ref=e15]:
- generic [ref=e16]:
- generic [ref=e17]:
- generic [ref=e18]:
- paragraph [ref=e19]: Morning Briefing
- heading "Good morning, JD" [level=1] [ref=e20]
- button "Run Full Research" [ref=e21]
- generic [ref=e22]:
- generic [ref=e23]:
- paragraph [ref=e24]: Holdings
- generic [ref=e25]:
- button "COST $921.40 +1.2% 32%" [ref=e26]:
- generic [ref=e27]: COST
- generic [ref=e28]: $921.40
- generic [ref=e29]: +1.2%
- generic [ref=e30]: 32%
- button "AMZN $186.50 +0.8% 28%" [ref=e31]:
- generic [ref=e32]: AMZN
- generic [ref=e33]: $186.50
- generic [ref=e34]: +0.8%
- generic [ref=e35]: 28%
- button "WMT $168.30 -0.3% 22%" [ref=e36]:
- generic [ref=e37]: WMT
- generic [ref=e38]: $168.30
- generic [ref=e39]: "-0.3%"
- generic [ref=e40]: 22%
- button "TGT $142.80 -1.1% 18%" [ref=e41]:
- generic [ref=e42]: TGT
- generic [ref=e43]: $142.80
- generic [ref=e44]: "-1.1%"
- generic [ref=e45]: 18%
- button "BJ $84.20 +0.4% 8%" [ref=e46]:
- generic [ref=e47]: BJ
- generic [ref=e48]: $84.20
- generic [ref=e49]: +0.4%
- generic [ref=e50]: 8%
- button "KR $62.10 -0.6% 5%" [ref=e51]:
- generic [ref=e52]: KR
- generic [ref=e53]: $62.10
- generic [ref=e54]: "-0.6%"
- generic [ref=e55]: 5%
- button "DG $78.50 +1.5% 4%" [ref=e56]:
- generic [ref=e57]: DG
- generic [ref=e58]: $78.50
- generic [ref=e59]: +1.5%
- generic [ref=e60]: 4%
- generic [ref=e61]:
- paragraph [ref=e62]: Portfolio Performance (6M)
- img [ref=e63]
- generic [ref=e67]:
- generic [ref=e68]: Nov
- generic [ref=e69]: Dec
- generic [ref=e70]: Jan
- generic [ref=e71]: Feb
- generic [ref=e72]: Mar
- generic [ref=e73]: Apr
- generic [ref=e74]:
- generic [ref=e75]:
- generic [ref=e76]: Total Value
- strong [ref=e77]: $2.41M
- generic [ref=e78]:
- generic [ref=e79]: Day P&L
- strong [ref=e80]: +$12,340
- generic [ref=e81]:
- generic [ref=e82]: Holdings
- strong [ref=e83]: "7"
- generic [ref=e84]:
- generic [ref=e85]: Beta
- strong [ref=e86]: "0.68"
- generic [ref=e87]:
- generic [ref=e88]: Sharpe
- strong [ref=e89]: "1.42"
- generic [ref=e90]:
- generic [ref=e91]: Max DD
- strong [ref=e92]: "-4.1%"
- generic [ref=e93]:
- paragraph [ref=e94]: Exposure
- generic [ref=e95]:
- generic [ref=e96]:
- generic [ref=e97]:
- strong [ref=e98]: Consumer Staples
- generic [ref=e99]: 56%
- generic [ref=e100]:
- strong [ref=e101]: Consumer Disc.
- generic [ref=e102]: 26%
- generic [ref=e103]:
- generic [ref=e104]:
- strong [ref=e105]: Technology
- generic [ref=e106]: 10%
- generic [ref=e107]:
- strong [ref=e108]: Health Care
- generic [ref=e109]: 8%
- generic [ref=e110]:
- generic [ref=e111]:
- generic [ref=e112]: Value
- generic [ref=e115]: "+0.32"
- generic [ref=e116]:
- generic [ref=e117]: Size
- generic [ref=e120]: "-0.48"
- generic [ref=e121]:
- generic [ref=e122]: Momentum
- generic [ref=e125]: "+0.18"
- generic [ref=e126]:
- generic [ref=e127]: Quality
- generic [ref=e130]: "+0.41"
- generic [ref=e131]:
- generic [ref=e132]: Low Vol
- generic [ref=e135]: "+0.27"
- generic [ref=e136]:
- generic [ref=e137]:
- paragraph [ref=e138]: Earnings Calendar
- generic [ref=e139]:
- generic [ref=e140]: Tue May 12
- generic [ref=e141]: COST Q3 FY25
- generic [ref=e142]: BMO
- generic [ref=e143]: 3d
- generic [ref=e144]:
- generic [ref=e145]: Wed May 13
- generic [ref=e146]: WMT Q1 FY27
- generic [ref=e147]: BMO
- generic [ref=e148]: 4d
- generic [ref=e149]:
- generic [ref=e150]: Thu May 14
- generic [ref=e151]: AMZN Q2 FY26
- generic [ref=e152]: AMC
- generic [ref=e153]: 5d
- generic [ref=e154]:
- paragraph [ref=e155]: Watchlist Alerts
- generic [ref=e156]:
- generic [ref=e157]: 2h ago
- generic [ref=e158]: COST comp traffic beat internal threshold
- generic [ref=e159]: Thesis +
- generic [ref=e160]:
- generic [ref=e161]: 4h ago
- generic [ref=e162]: WMT price investment pressuring grocery basket
- generic [ref=e163]: Thesis -
- generic [ref=e164]:
- generic [ref=e165]: 1d ago
- generic [ref=e166]: New 10-Q filed for TGT
- generic [ref=e167]: Filing
- generic [ref=e168]:
- paragraph [ref=e169]: Recent Reports & Exports
- generic [ref=e170]:
- button "Investment Memo Membership Economics at Scale Draft - 2 reviews" [ref=e171]:
- generic [ref=e172]: Investment Memo
- strong [ref=e173]: Membership Economics at Scale
- generic [ref=e174]: Draft - 2 reviews
- button "Financial Model Revenue Build FY25-FY27 Base / Bull / Bear" [ref=e175]:
- generic [ref=e176]: Financial Model
- strong [ref=e177]: Revenue Build FY25-FY27
- generic [ref=e178]: Base / Bull / Bear
- button "Earnings Update Q2 FY25 Earnings Note Auto-generated - Mar 6" [ref=e179]:
- generic [ref=e180]: Earnings Update
- strong [ref=e181]: Q2 FY25 Earnings Note
- generic [ref=e182]: Auto-generated - Mar 6
- button "Peer Comparison Warehouse Club Margins COST vs WMT, TGT, BJ" [ref=e183]:
- generic [ref=e184]: Peer Comparison
- strong [ref=e185]: Warehouse Club Margins
- generic [ref=e186]: COST vs WMT, TGT, BJ
- button "Valuation DCF + Comps Analysis $720-$1,280 range" [ref=e187]:
- generic [ref=e188]: Valuation
- strong [ref=e189]: DCF + Comps Analysis
- generic [ref=e190]: $720-$1,280 range
- button "Risk Register Risk & Mitigant Framework 8 risks catalogued" [ref=e191]:
- generic [ref=e192]: Risk Register
- strong [ref=e193]: Risk & Mitigant Framework
- generic [ref=e194]: 8 risks catalogued
- generic [ref=e195]:
- paragraph [ref=e196]: Pending Reviews
- generic [ref=e197]:
- generic [ref=e198]: Memo
- generic [ref=e199]: Variant perception needs consensus margin citation
- generic [ref=e200]: 2 comments
- generic [ref=e201]:
- generic [ref=e202]: Model
- generic [ref=e203]: FY26 warehouse openings assumption flagged
- generic [ref=e204]: Review
- generic [ref=e205]:
- paragraph [ref=e206]: Agent Status
- generic [ref=e207]:
- generic [ref=e209]:
- strong [ref=e210]: SEC Filings Agent
- paragraph [ref=e211]: Extracting segment data from 10-K
- generic [ref=e212]: 45%
- generic [ref=e213]:
- generic [ref=e215]:
- strong [ref=e216]: Financial Modeling Agent
- paragraph [ref=e217]: Building revenue schedule
- generic [ref=e218]: 62%
- generic [ref=e219]:
- generic [ref=e221]:
- strong [ref=e222]: Earnings Call Agent
- paragraph [ref=e223]: Summarizing Q2 FY25 call
- generic [ref=e224]: 78%
- contentinfo [ref=e225]:
- generic [ref=e226]:
- button "<" [disabled] [ref=e227]
- strong [ref=e229]: SEC Filings Agent
- generic [ref=e230]: Extracting segment data from 10-K
- generic [ref=e231]: 1 / 3
- button ">" [ref=e232]
- generic [ref=e233]:
- text: "> Ask an agent"
- generic [ref=e234]: Cmd K

View File

@@ -0,0 +1,193 @@
- generic [ref=e3]:
- banner [ref=e4]:
- button "MosaicIQ" [ref=e5] [cursor=pointer]
- generic [ref=e235]:
- generic [ref=e236]: COST
- generic [ref=e237]: Costco Wholesale Corp
- generic [ref=e238]: $921.40
- generic [ref=e239]: +1.2%
- navigation "Screens" [ref=e6]:
- button "Home" [ref=e7]
- button "Workspace" [ref=e8]
- button "Model" [ref=e9]
- button "Memo" [active] [ref=e10]
- button "Agents" [ref=e11]
- textbox "Search companies, files, commands..." [ref=e12]
- button "Settings" [ref=e13]: ⚙
- button "JD" [ref=e14]
- main [ref=e15]:
- generic [ref=e240]:
- complementary [ref=e241]:
- generic [ref=e242]:
- paragraph [ref=e243]: Memo Outline
- button "Collapse memo outline" [ref=e244]: <
- button "01Investment Thesis" [ref=e245]
- button "02Key Drivers" [ref=e246]
- button "03Variant Perception" [ref=e247]
- button "04Valuation" [ref=e248]
- button "05Business Quality" [ref=e249]
- button "06Financial Summary" [ref=e250]
- button "07Risks & Mitigants" [ref=e251]
- button "08Catalysts" [ref=e252]
- article [ref=e253]:
- generic [ref=e254]:
- generic [ref=e255]: Draft
- generic [ref=e256]: Saved
- button "Review Mode" [ref=e257]
- button "Export PDF" [ref=e258]
- generic [ref=e259]: Blocked by unapproved sections
- button "Publish" [disabled] [ref=e260]
- generic [ref=e261]:
- paragraph [ref=e262]: COST - Nasdaq - Consumer Staples
- heading "COST Investment Memo" [level=1] [ref=e263]
- paragraph [ref=e264]: Prepared by JD - May 2026 - Confidential
- generic [ref=e265]:
- heading "Investment Thesis title" [level=3] [ref=e266]: Investment Thesis
- textbox "Investment Thesis content" [ref=e267]:
- paragraph [ref=e268]:
- text: Costco remains a high-quality compounder with unusually durable traffic, renewal, and private-label economics. The core thesis is that membership fee income, disciplined SKU curation, and steady warehouse productivity can support high-single-digit earnings growth over a multi-year horizon, even as the
- mark [ref=e269]: current valuation requires disciplined sensitivity work
- text: around renewal fees, wage inflation, and merchandise margin.
- paragraph [ref=e270]: "[1]"
- generic [ref=e271]:
- text: "AI suggestion: tighten the fee-increase sentence and quantify renewal-cycle timing."
- generic [ref=e272]:
- button "Accept" [ref=e273]
- button "Reject" [ref=e274]
- button "Revise" [ref=e275]
- generic [ref=e276]:
- heading "Key Drivers title" [level=3] [ref=e277]: Key Drivers
- textbox "Key Drivers content" [ref=e278]:
- paragraph [ref=e279]:
- text: The model is most sensitive to
- mark [ref=e280]: membership fee cadence
- text: ", comparable sales excluding fuel, and operating leverage across warehouse labor and logistics. A 50 bps change in core merchandise margin or a one-year shift in fee timing drives a disproportionate share of the bear-to-bull spread."
- paragraph [ref=e281]: "[2]"
- generic [ref=e282]:
- heading "Variant Perception title" [level=3] [ref=e283]: Variant Perception
- textbox "Variant Perception content" [ref=e284]:
- paragraph [ref=e285]: Consensus treats Costco as a fully discovered quality compounder, but underweights the durability of traffic share gains in grocery and consumables. The variant view is that renewal behavior and executive member penetration create more operating resilience than the market is giving credit for during a slower discretionary cycle.
- paragraph [ref=e286]: "[3]"
- generic [ref=e287]:
- heading "Valuation title" [level=3] [ref=e288]: Valuation
- textbox "Valuation content" [ref=e289]:
- paragraph [ref=e290]:
- text: The base case triangulates a premium earnings multiple, a DCF anchored on
- mark [ref=e291]: low-teens discount-rate sensitivity
- text: ", and peer multiples against scaled staples and retail platforms. The current share price embeds limited margin for execution misses, so valuation work should frame upside through fee timing and downside through wage and shrink pressure."
- paragraph [ref=e292]: "[4]"
- generic [ref=e293]:
- heading "Business Quality title" [level=3] [ref=e294]: Business Quality
- textbox "Business Quality content" [ref=e295]:
- paragraph [ref=e296]: Costco's moat is built on purchasing scale, a low-markup operating philosophy, a limited-SKU model, and recurring membership economics. ROIC remains supported by high inventory turns and negative working capital dynamics, while management quality is reflected in disciplined capital allocation and consistent reinvestment in member value.
- paragraph [ref=e297]: "[5]"
- generic [ref=e298]:
- heading "Financial Summary title" [level=3] [ref=e299]: Financial Summary
- textbox "Financial Summary content" [ref=e300]:
- paragraph [ref=e301]: Revenue growth is expected to track warehouse expansion, comparable sales excluding fuel, and modest e-commerce contribution. Margin analysis should separate merchandise gross margin, membership fee income, wage inflation, logistics costs, and fuel volatility so the model does not overstate operating leverage.
- paragraph [ref=e302]: "[6]"
- generic [ref=e303]:
- heading "Risks & Mitigants title" [level=3] [ref=e304]: Risks & Mitigants
- textbox "Risks & Mitigants content" [ref=e305]:
- paragraph [ref=e306]: Key risks include valuation compression, delayed fee increases, labor cost inflation, weaker discretionary categories, and international execution risk. Mitigants include renewal-rate stability, grocery-led traffic, balance-sheet flexibility, and management's demonstrated willingness to protect the member value proposition through cycles.
- paragraph [ref=e307]: "[7]"
- generic [ref=e308]:
- heading "Catalysts title" [level=3] [ref=e309]: Catalysts
- textbox "Catalysts content" [ref=e310]:
- paragraph [ref=e311]: Near-term catalysts include membership fee announcements, monthly sales reports, executive member penetration updates, new warehouse openings, and quarterly commentary on traffic versus ticket. A clean fee-increase signal with stable renewal metrics would likely be the most important rerating event.
- paragraph [ref=e312]: "[8]"
- complementary [ref=e313]:
- paragraph [ref=e314]: Review Tools
- generic [ref=e315]:
- button "Highlight" [disabled] [ref=e316]
- button "Comment" [disabled] [ref=e317]
- button "Strike" [disabled] [ref=e318]
- button "Draw Box" [disabled] [ref=e319]
- paragraph [ref=e320]: Turn on review mode to annotate sections and citations.
- paragraph [ref=e321]: Active Section Status
- generic [ref=e322]:
- button "Pending" [ref=e323]
- button "In Review" [ref=e324]
- button "Approved" [ref=e325]
- button "Changes Requested" [ref=e326]
- paragraph [ref=e327]: Citations
- generic [ref=e328]:
- generic [ref=e329]: "[1] SEC Filing"
- generic [ref=e330]: FY2024 10-K - Membership economics
- generic [ref=e331]: Verified - Annual report discussion of membership fee income and renewal rates
- generic [ref=e332]:
- generic [ref=e333]: "[2] Earnings Transcript - Other section"
- generic [ref=e334]: Q2 FY2025 earnings call - Executive tier metrics
- generic [ref=e335]: Verified - Management commentary on executive member penetration and traffic
- generic [ref=e336]:
- generic [ref=e337]: "[3] Analyst Report - Other section"
- generic [ref=e338]: Consensus margin sensitivity note
- generic [ref=e339]: Unverified - External analyst framing of merchandise margin risk
- generic [ref=e340]:
- generic [ref=e341]: "[4] Model - Other section"
- generic [ref=e342]: Internal DCF sensitivity model
- generic [ref=e343]: Flagged - Discount-rate and terminal multiple sensitivity output
- generic [ref=e344]:
- generic [ref=e345]: "[5] Internal Note - Other section"
- generic [ref=e346]: Risk register - Labor and fee timing
- generic [ref=e347]: Verified - Internal notes from source verification pass
- paragraph [ref=e348]: Review Status
- generic [ref=e349]:
- generic [ref=e350]: Investment Thesis
- generic [ref=e351]: Approved
- generic [ref=e352]:
- generic [ref=e353]: Key Drivers
- generic [ref=e354]: In Review
- generic [ref=e355]:
- generic [ref=e356]: Variant Perception
- generic [ref=e357]: In Review
- generic [ref=e358]:
- generic [ref=e359]: Valuation
- generic [ref=e360]: Changes Requested
- generic [ref=e361]:
- generic [ref=e362]: Business Quality
- generic [ref=e363]: Pending
- generic [ref=e364]:
- generic [ref=e365]: Financial Summary
- generic [ref=e366]: Pending
- generic [ref=e367]:
- generic [ref=e368]: Risks & Mitigants
- generic [ref=e369]: Pending
- generic [ref=e370]:
- generic [ref=e371]: Catalysts
- generic [ref=e372]: Pending
- paragraph [ref=e373]: Comments
- generic [ref=e374]:
- generic [ref=e375]:
- generic [ref=e376]:
- generic [ref=e377]: JD
- generic [ref=e378]: Investment Thesis
- generic [ref=e379]: Comment
- paragraph [ref=e380]: Quantify target price range before IC circulation.
- paragraph [ref=e381]: "\"current valuation requires disciplined sensitivity work\""
- button "Resolve" [ref=e382]
- generic [ref=e383]:
- generic [ref=e384]:
- generic [ref=e385]: MW
- generic [ref=e386]: Key Drivers
- generic [ref=e387]: Comment
- paragraph [ref=e388]: Tie fee cadence to the model sensitivity table.
- paragraph [ref=e389]: "\"membership fee cadence\""
- button "Resolve" [ref=e390]
- generic [ref=e391]:
- generic [ref=e392]:
- generic [ref=e393]: SV
- generic [ref=e394]: Valuation
- generic [ref=e395]: Highlight
- paragraph [ref=e396]: low-teens discount-rate sensitivity
- button "Resolve" [ref=e397]
- contentinfo [ref=e225]:
- generic [ref=e226]:
- button "<" [disabled] [ref=e227]
- strong [ref=e229]: SEC Filings Agent
- generic [ref=e230]: Extracting segment data from 10-K
- generic [ref=e231]: 1 / 3
- button ">" [ref=e232]
- generic [ref=e233]:
- text: "> Ask an agent"
- generic [ref=e234]: Cmd K

111
agent.md Normal file
View File

@@ -0,0 +1,111 @@
# Agent Instructions
You are helping implement **MosaicIQ**, an AI-native equity research workspace.
Use the attached **MosaicIQ Design Document v3** as the primary reference for product intent, UX direction, architecture, screen specs, agent behavior, and implementation details. This file is intentionally light; the design doc contains the fuller guidance.
## Core Direction
Build a clean, fast, local-first equity research application with a **t3-code-style architecture**:
- Clear client/server boundary
- Typed RPC layer for all client/server communication
- Local-first persistence
- Simple, composable React components
- Minimal magic, strong types, predictable state
- Practical implementation over over-engineering
## Product Identity
The product name is **MosaicIQ**.
Do not use old/internal names like “Meridian” in user-facing UI unless the design doc explicitly says it is internal.
## Design Feel
Follow the design docs editorial research-workstation style:
- Serif display typography for headings
- Monospace for tickers, numbers, metadata, financial tables, and code-like labels
- Sans-serif for body/UI text
- Warm neutral palette with one restrained rust accent
- Hairline borders and whitespace instead of heavy cards, shadows, or gradients
- Dense but calm layout for analysts working on desktop screens
Keep the UI serious, analytical, and professional.
## Architecture Rules
Prefer the structure described in the design doc:
- Client never reads or writes storage directly
- Client talks to the server through typed RPC methods
- Server owns SQLite persistence, agent orchestration, file I/O, and export logic
- Client owns UI state such as active screen, panels, collapsed nav, search, and display preferences
- Use schema validation and safe defaults for settings
- Keep the path open for future remote/server deployment
## Agent Behavior
Agents should feel like collaborative research analysts, not black-box tools.
They should:
- Surface assumptions
- Cite or reference source material when possible
- Flag uncertainty
- Ask for clarification only when needed
- Support review, acceptance, rejection, and revision workflows
- Avoid pushing unvalidated conclusions directly into final outputs
Use the design docs agent system, validation loop, and screen guidance for details.
## Implementation Priorities
Start simple and build in layers:
1. App shell and navigation
2. Portfolio/company workspace
3. Financial model table UI
4. Memo editor
5. Agent orchestration UI
6. RPC/server persistence
7. Export flows
8. Validation/review loops
Do not try to implement everything at once. Favor small, working vertical slices.
## Coding Style
- Use TypeScript throughout
- Keep components small and readable
- Prefer explicit state and typed data contracts
- Avoid unnecessary abstractions
- Avoid large monolithic files
- Keep styling consistent with the design tokens in the design doc
- Make empty, loading, error, and disabled states feel intentional
## Dev Server
**NEVER start, stop, restart, or manage the app/dev server.** Do not run `npm run dev`, `pnpm dev`, `yarn dev`, `vite`, `vite preview`, Electron launch commands, or equivalent server/app-start commands.
The user runs the app independently. Your job is to write/edit code and run non-server verification commands such as typecheck, lint, tests, or production build when requested or appropriate.
Do not open or navigate a browser to the local app unless the user explicitly asks for browser inspection and confirms that they already have the app running.
## When Unsure
Refer back to the MosaicIQ Design Document v3. It contains additional helpful information on:
- Design tokens
- Screen specifications
- Component behavior
- RPC methods
- SSE events
- Data/state shape
- Persistence/versioning
- Agent workflows
- Export center
- Accessibility and keyboard shortcuts
Default to the design doc unless it conflicts with a newer explicit instruction.

View File

@@ -0,0 +1,5 @@
{
"name": "@mosaiciq/desktop",
"private": true,
"type": "module"
}

View File

@@ -0,0 +1,7 @@
# Desktop Source Layout
- `main.ts`: Electron app lifecycle and browser window setup.
- `preload.ts`: isolated bridge exposed to the renderer.
- `rpc.ts`: local RPC method handling.
The desktop app owns Electron APIs. Renderer-facing types come from `packages/contracts`, and reusable non-Electron data belongs in `packages/shared`.

46
apps/desktop/src/main.ts Normal file
View File

@@ -0,0 +1,46 @@
import { app, BrowserWindow, ipcMain } from "electron";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { handleRpc } from "./rpc.js";
import type { RpcMethod, RpcRequestMap } from "../../../packages/contracts/src/rpc.js";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const isDev = process.env.VITE_DEV_SERVER_URL || !app.isPackaged;
async function createWindow() {
const win = new BrowserWindow({
width: 1440,
height: 960,
minWidth: 1024,
minHeight: 700,
title: "MosaicIQ",
backgroundColor: "#f8f5ed",
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: false
}
});
if (isDev) {
await win.loadURL(process.env.VITE_DEV_SERVER_URL ?? "http://127.0.0.1:5173");
win.webContents.openDevTools({ mode: "detach" });
} else {
await win.loadFile(path.join(__dirname, "../../../../apps/web/dist/index.html"));
}
}
ipcMain.handle("rpc:call", (_event, method: RpcMethod, payload: RpcRequestMap[RpcMethod]) => {
return handleRpc(method, payload);
});
app.whenReady().then(createWindow);
app.on("window-all-closed", () => {
if (process.platform !== "darwin") app.quit();
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) void createWindow();
});

View File

@@ -0,0 +1,21 @@
import { contextBridge, ipcRenderer } from "electron";
import type { RpcClient, RpcMethod, RpcRequestMap } from "../../../packages/contracts/src/rpc.js";
const api: RpcClient = {
call(method, payload) {
return ipcRenderer.invoke("rpc:call", method, payload);
}
};
contextBridge.exposeInMainWorld("mosaic", api);
declare global {
interface Window {
mosaic: {
call<T extends RpcMethod>(
method: T,
payload: RpcRequestMap[T]
): Promise<import("../../../packages/contracts/src/rpc.js").RpcResult<T>>;
};
}
}

9
apps/desktop/src/rpc.ts Normal file
View File

@@ -0,0 +1,9 @@
import type { RpcMethod, RpcRequestMap, RpcResult } from "../../../packages/contracts/src/rpc.js";
import { handleMockRpc } from "../../../packages/shared/src/mockRpc.js";
export async function handleRpc<T extends RpcMethod>(
method: T,
payload: RpcRequestMap[T]
): Promise<RpcResult<T>> {
return handleMockRpc(method, payload);
}

12
apps/web/index.html Normal file
View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>MosaicIQ</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

9
apps/web/package.json Normal file
View File

@@ -0,0 +1,9 @@
{
"name": "@mosaiciq/web",
"private": true,
"type": "module",
"dependencies": {
"@mosaiciq/contracts": "workspace:*",
"@mosaiciq/shared": "workspace:*"
}
}

8
apps/web/src/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Web Source Layout
- `main.tsx`: renderer bootstrap.
- `ui/`: React UI components.
- `rpcClient.ts`: browser-side client for the preload RPC bridge.
- `styles.css`: renderer styles.
Keep Electron-specific code out of this app. Shared message shapes belong in `packages/contracts`.

9
apps/web/src/global.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
import type { RpcClient } from "../../../packages/contracts/src/rpc";
declare global {
interface Window {
mosaic?: RpcClient;
}
}
export {};

3
apps/web/src/lib/cn.ts Normal file
View File

@@ -0,0 +1,3 @@
export function cx(...classes: Array<string | false | null | undefined>) {
return classes.filter(Boolean).join(" ");
}

View File

@@ -0,0 +1,97 @@
import type { Alert, Catalyst, EarningsSchedule, ExportRecord, Filing, Holding, Risk, Screen } from "@mosaiciq/contracts/rpc";
export const screens: Screen[] = ["home", "workspace", "model", "memo", "agents"];
export const workspaceGroups = [
["Research", "Company Snapshot", "Business Description", "Segment / Revenue Build", "Margin Build", "Historical Financials", "Three-Statement Model", "Key KPIs", "Management & Strategy"],
["Analysis", "Competitive Landscape", "Peer Comparison", "Valuation Analysis", "Investment Thesis", "Risks & Mitigants", "Catalyst Tracker"],
["Monitoring", "Earnings Monitor", "Filing Watch", "Thesis Alerts"],
["Library", "Source Library", "Export Center"]
];
export const agentCatalog: Array<[string, string, string, string]> = [
["cr", "Company Research Agent", "Structured profiles from filings, transcripts, external data", "research"],
["sf", "SEC Filings Agent", "Segment data, KPIs, risk factors, accounting policies", "research"],
["fm", "Financial Modeling Agent", "Revenue builds, margin models, three-statement frameworks", "research"],
["ec", "Earnings Call Agent", "Management tone, guidance, KPIs, Q&A themes", "competitive"],
["ci", "Competitive Intel Agent", "Peer analysis, market positioning, competitive threats", "competitive"],
["va", "Valuation Agent", "DCF, trading comps, scenario analysis, multiples", "research"],
["rk", "Risk Agent", "Business, financial, competitive, regulatory risks", "competitive"],
["mw", "Memo Writing Agent", "Investment memos, research reports, IC memos", "research"],
["pa", "Presentation Agent", "IC presentation drafts, slide outlines, exhibits", "research"],
["mn", "Monitoring Agent", "Filing alerts, thesis changes, earnings events", "cross-cutting"],
["sv", "Source Verification Agent", "Citation checking, source reliability, cross-referencing", "cross-cutting"],
["rt", "Red Team Agent", "Thesis challenges, assumption stress-testing, bear cases", "competitive"],
["ex", "Export Agent", "PDF, Excel, PowerPoint export pipelines", "cross-cutting"],
["qa", "Model QA Agent", "Formula auditing, balance sheet checks, sanity tests", "cross-cutting"]
];
export const extraHoldings: Holding[] = [
{ ticker: "BJ", name: "BJ's Wholesale Club", price: 84.2, changePct: 0.4, weight: 8 },
{ ticker: "KR", name: "Kroger Co", price: 62.1, changePct: -0.6, weight: 5 },
{ ticker: "DG", name: "Dollar General", price: 78.5, changePct: 1.5, weight: 4 }
];
export const demoCatalysts: Catalyst[] = [
{ id: "cat-1", date: "2026-06-05", event: "Q3 FY25 Earnings", impact: "high", thesisRelevance: "supports", source: "[4]" },
{ id: "cat-2", date: "2026-07-15", event: "Executive member update", impact: "medium", thesisRelevance: "supports", source: "[2]" },
{ id: "cat-3", date: "2026-08-01", event: "Annual membership fee review", impact: "high", thesisRelevance: "neutral", source: "[1]" },
{ id: "cat-4", date: "2026-09-10", event: "New warehouse openings (Q4)", impact: "low", thesisRelevance: "supports", source: "[1]" }
];
export const demoAlerts: Alert[] = [
{ id: "alert-1", companyId: "cost", timestamp: "2026-05-12T08:30:00Z", type: "earnings_surprise", description: "Q2 FY25 earnings beat (+7.5% comp)", thesisImpact: "positive", status: "new", targetSection: "thesis" },
{ id: "alert-2", companyId: "cost", timestamp: "2026-05-12T06:00:00Z", type: "filing", description: "New 8-K: Executive compensation update", thesisImpact: "neutral", status: "new" },
{ id: "alert-3", companyId: "wmt", timestamp: "2026-05-11T14:00:00Z", type: "peer_event", description: "WMT announces price investment in grocery", thesisImpact: "negative", status: "reviewed" },
{ id: "alert-4", companyId: "cost", timestamp: "2026-05-11T10:00:00Z", type: "price_move", description: "COST +2.1% on heavy volume", thesisImpact: "positive", status: "reviewed" }
];
export const demoRisks: Risk[] = [
{ id: "risk-1", companyId: "cost", risk: "Amazon enters warehouse club segment", category: "competitive", severity: "high", likelihood: "low", mitigation: "Costco's 93% renewal rate creates switching costs", status: "open" },
{ id: "risk-2", companyId: "cost", risk: "Wage inflation compresses operating margin", category: "financial", severity: "medium", likelihood: "high", mitigation: "Automation and productivity offset 40-60% of wage pressure", status: "open" },
{ id: "risk-3", companyId: "cost", risk: "Membership fee increase delayed beyond FY26", category: "financial", severity: "medium", likelihood: "medium", mitigation: "Fee income growth from executive tier conversion", status: "mitigated" },
{ id: "risk-4", companyId: "cost", risk: "Regulatory pressure on merchandise sourcing", category: "regulatory", severity: "low", likelihood: "low", mitigation: "Diversified supply chain across 14 countries", status: "accepted" },
{ id: "risk-5", companyId: "cost", risk: "E-commerce disruption of warehouse model", category: "competitive", severity: "medium", likelihood: "medium", mitigation: "Costco.com growth and Instacart partnership", status: "open" },
{ id: "risk-6", companyId: "cost", risk: "Valuation compression on growth deceleration", category: "financial", severity: "high", likelihood: "medium", mitigation: "High-single-digit earnings growth supports premium", status: "open" }
];
export const demoEarnings: EarningsSchedule[] = [
{ id: "earn-1", companyId: "cost", quarter: "Q3 FY25", expectedDate: "2026-06-05", timing: "bmo" },
{ id: "earn-2", companyId: "cost", quarter: "Q4 FY25", expectedDate: "2026-09-25", timing: "bmo" },
{ id: "earn-3", companyId: "cost", quarter: "Q2 FY25", expectedDate: "2026-03-06", timing: "bmo", actualRevenue: "$62.5B", expectedRevenue: "$61.2B", actualEps: "$4.28", expectedEps: "$4.05" },
{ id: "earn-4", companyId: "cost", quarter: "Q1 FY25", expectedDate: "2025-12-12", timing: "bmo", actualRevenue: "$60.2B", expectedRevenue: "$59.8B", actualEps: "$4.04", expectedEps: "$3.98" }
];
export const demoFilings: Filing[] = [
{ id: "filing-1", companyId: "cost", formType: "10-K", filedDate: "2024-10-10", title: "Annual Report FY2024", keyChanges: "Updated segment reporting, new warehouse commitments ($2.1B)", reviewed: true },
{ id: "filing-2", companyId: "cost", formType: "10-Q", filedDate: "2026-03-10", title: "Quarterly Report Q2 FY25", keyChanges: "Membership fee income growth, margin expansion", reviewed: true },
{ id: "filing-3", companyId: "cost", formType: "8-K", filedDate: "2026-05-12", title: "Executive Compensation Update", keyChanges: "New CEO compensation structure", reviewed: false }
];
export const demoExports: ExportRecord[] = [
{ id: "export-1", type: "excel", title: "COST Model — Revenue Build FY25-FY27", companyId: "cost", format: "Excel", fileSize: "2.4 MB", status: "complete", createdAt: "2026-05-10T14:30:00Z" },
{ id: "export-2", type: "pdf", title: "COST Investment Memo — Draft", companyId: "cost", format: "PDF", fileSize: "1.8 MB", status: "complete", createdAt: "2026-05-09T16:00:00Z" },
{ id: "export-3", type: "ppt", title: "COST IC Presentation", companyId: "cost", format: "PowerPoint", fileSize: "4.2 MB", status: "processing", createdAt: "2026-05-12T09:00:00Z" },
{ id: "export-4", type: "pdf", title: "Peer Comparison Report", companyId: "cost", format: "PDF", fileSize: "980 KB", status: "complete", createdAt: "2026-05-08T11:00:00Z" }
];
export const keyboardShortcuts: Array<[string, string, string]> = [
["⌘K", "Open agent chat / command bar", "global"],
["⌘F", "Focus search bar", "global"],
["⌘S", "Create manual snapshot", "memo,model"],
["⌘E", "Open export quick-action menu", "global"],
["⌘\\", "Toggle left nav collapse", "workspace,memo"],
["⌘1-5", "Switch to screen 1-5", "global"],
["⌘.", "Toggle settings overlay", "global"],
["Escape", "Close overlay / fullscreen / search", "global"],
["↑ / ↓", "Navigate carousel, search, model cells", "context"],
["Enter", "Select / confirm", "context"],
["Tab", "Move between memo sections", "memo"],
["⌘Z", "Undo last edit", "memo,model"],
["⌘⇧Z", "Redo", "memo,model"]
];
export const modelTabs = [
"Revenue Build", "Income Statement", "Balance Sheet", "Cash Flow Statement",
"Margin Build", "Tax Build", "LIFO / FIFO Conversion", "Scenario Analysis", "Charts"
];

View File

@@ -0,0 +1,15 @@
export function formatPct(value: number) {
return `${value > 0 ? "+" : ""}${value.toFixed(1)}%`;
}
export function formatTime(value: string) {
return new Intl.DateTimeFormat(undefined, { hour: "2-digit", minute: "2-digit" }).format(new Date(value));
}
export function capitalize(value: string) {
return value.charAt(0).toUpperCase() + value.slice(1);
}
export function toneText(value: number) {
return value >= 0 ? "text-[var(--green)]" : "text-[var(--red)]";
}

View File

@@ -0,0 +1,336 @@
import type { MemoAnnotation } from "@mosaiciq/contracts/rpc";
import { cx } from "./cn";
export function markdownToEditableHtml(content: string, annotations: MemoAnnotation[] = []) {
if (!content) return "";
const lines = content.split(/\r?\n/);
const blocks: string[] = [];
for (let index = 0; index < lines.length; index += 1) {
const line = lines[index];
if (!line.trim()) {
blocks.push("<p><br></p>");
continue;
}
const unorderedItems: string[] = [];
while (index < lines.length) {
const match = lines[index].match(/^\s*[-*]\s+(.+)$/);
if (!match) break;
unorderedItems.push(match[1]);
index += 1;
}
if (unorderedItems.length) {
index -= 1;
blocks.push(`<ul>${unorderedItems.map((item) => `<li>${renderEditableInlineMarkdown(item, annotations)}</li>`).join("")}</ul>`);
continue;
}
const orderedItems: string[] = [];
while (index < lines.length) {
const match = lines[index].match(/^\s*\d+\.\s+(.+)$/);
if (!match) break;
orderedItems.push(match[1]);
index += 1;
}
if (orderedItems.length) {
index -= 1;
blocks.push(`<ol>${orderedItems.map((item) => `<li>${renderEditableInlineMarkdown(item, annotations)}</li>`).join("")}</ol>`);
continue;
}
const heading = line.match(/^(#{1,3})\s+(.+)$/);
if (heading) {
const level = Math.min(heading[1].length + 3, 6);
blocks.push(`<h${level}>${renderEditableInlineMarkdown(heading[2], annotations)}</h${level}>`);
continue;
}
const quote = line.match(/^>\s+(.+)$/);
if (quote) {
blocks.push(`<blockquote>${renderEditableInlineMarkdown(quote[1], annotations)}</blockquote>`);
continue;
}
blocks.push(`<p>${renderEditableInlineMarkdown(line, annotations)}</p>`);
}
return blocks.join("");
}
function renderEditableInlineMarkdown(value: string, annotations: MemoAnnotation[] = []) {
const pattern = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]+\]\([^)]+\)|\*[^*]+\*)/g;
let cursor = 0;
let html = "";
let match: RegExpExecArray | null;
while ((match = pattern.exec(value)) !== null) {
if (match.index > cursor) html += renderAnnotatedPlainText(value.slice(cursor, match.index), annotations);
const token = match[0];
if (token.startsWith("**")) {
html += `<strong>${escapeHtml(token.slice(2, -2))}</strong>`;
} else if (token.startsWith("*")) {
html += `<em>${escapeHtml(token.slice(1, -1))}</em>`;
} else if (token.startsWith("`")) {
html += `<code>${escapeHtml(token.slice(1, -1))}</code>`;
} else {
const link = token.match(/^\[([^\]]+)\]\(([^)]+)\)$/);
html += link ? `<a href="${escapeAttribute(link[2])}" rel="noreferrer">${escapeHtml(link[1])}</a>` : escapeHtml(token);
}
cursor = match.index + token.length;
}
if (cursor < value.length) html += renderAnnotatedPlainText(value.slice(cursor), annotations);
return html;
}
function renderAnnotatedPlainText(value: string, annotations: MemoAnnotation[]) {
const candidates = annotations.filter((annotation) => annotation.selectedText);
let cursor = 0;
let html = "";
while (cursor < value.length) {
const match = findNextAnnotationMatch(value, candidates, cursor);
if (!match) {
html += escapeHtml(value.slice(cursor));
break;
}
if (match.index > cursor) html += escapeHtml(value.slice(cursor, match.index));
const matchingAnnotations = candidates.filter((annotation) => annotation.selectedText === match.text);
html += `<mark class="${annotationClassName(matchingAnnotations)}" data-annotation-id="${escapeAttribute(matchingAnnotations[0].id)}">${escapeHtml(match.text)}</mark>`;
cursor = match.index + match.text.length;
}
return html;
}
function findNextAnnotationMatch(value: string, annotations: MemoAnnotation[], offset: number) {
let bestMatch: { index: number; text: string } | null = null;
for (const annotation of annotations) {
const index = value.indexOf(annotation.selectedText, offset);
if (index === -1) continue;
if (
!bestMatch ||
index < bestMatch.index ||
(index === bestMatch.index && annotation.selectedText.length > bestMatch.text.length)
) {
bestMatch = { index, text: annotation.selectedText };
}
}
return bestMatch;
}
function annotationClassName(annotations: MemoAnnotation[]) {
const kinds = new Set(annotations.map((annotation) => annotation.kind));
return cx(
"px-0.5",
kinds.has("highlight") && "bg-[oklch(92%_0.08_95)]",
kinds.has("comment") && "bg-[oklch(92%_0.06_80)] underline decoration-[var(--accent)] decoration-2 underline-offset-2",
kinds.has("strike") && "line-through decoration-[var(--red)] decoration-2"
);
}
export function getAnnotationSignature(annotations: MemoAnnotation[]) {
return annotations
.map((annotation) => `${annotation.id}:${annotation.kind}:${annotation.status}:${annotation.selectedText}`)
.join("|");
}
export function getSelectionWithin(root: HTMLElement | null) {
if (!root) return "";
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return "";
const range = selection.getRangeAt(0);
if (!root.contains(range.commonAncestorContainer)) return "";
return selection.toString().trim();
}
export function editableHtmlToMarkdown(root: HTMLElement) {
const blocks = Array.from(root.childNodes).flatMap((node) => serializeBlockNode(node));
return joinMarkdownBlocks(blocks);
}
function joinMarkdownBlocks(blocks: string[]) {
if (blocks.every((block) => !block.trim())) return "";
return blocks.reduce((markdown, block, index) => {
if (index === 0) return block;
const previous = markdown.split("\n").at(-1) ?? "";
const separator = !previous.trim() || !block.trim() || shouldKeepAdjacent(previous, block) ? "\n" : "\n";
return `${markdown}${separator}${block}`;
}, "");
}
function shouldKeepAdjacent(previous: string, next: string) {
return (
(/^[-*]\s+\S/.test(previous) && /^[-*]\s+\S/.test(next)) ||
(/^\d+\.\s+\S/.test(previous) && /^\d+\.\s+\S/.test(next)) ||
(/^>\s+\S/.test(previous) && /^>\s+\S/.test(next))
);
}
function serializeBlockNode(node: ChildNode): string[] {
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent?.trim();
return text ? [text] : [];
}
if (!(node instanceof HTMLElement)) return [];
const tagName = node.tagName.toLowerCase();
if (tagName === "ul") {
return Array.from(node.children)
.filter((child) => child.tagName.toLowerCase() === "li")
.map((child) => `- ${serializeInlineNode(child).trim()}`)
.filter((line) => line.length > 2);
}
if (tagName === "ol") {
return Array.from(node.children)
.filter((child) => child.tagName.toLowerCase() === "li")
.map((child, index) => `${index + 1}. ${serializeInlineNode(child).trim()}`)
.filter((line) => /\d+\.\s+\S/.test(line));
}
if (/^h[1-6]$/.test(tagName)) {
const level = Math.min(Math.max(Number(tagName.slice(1)) - 3, 1), 3);
const text = serializeInlineNode(node).trim();
return text ? [`${"#".repeat(level)} ${text}`] : [];
}
if (tagName === "blockquote") {
const text = serializeInlineNode(node).trim();
return text ? text.split(/\r?\n/).map((line) => `> ${line}`) : [];
}
if (tagName === "div" || tagName === "p") {
const text = serializeInlineNode(node).trim();
return text ? [text] : [""];
}
const text = serializeInlineNode(node).trim();
return text ? [text] : [];
}
function serializeInlineNode(node: ChildNode): string {
if (node.nodeType === Node.TEXT_NODE) return node.textContent ?? "";
if (!(node instanceof HTMLElement)) return "";
const tagName = node.tagName.toLowerCase();
if (tagName === "br") return "\n";
const text = Array.from(node.childNodes).map((child) => serializeInlineNode(child)).join("");
if (!text) return "";
if (tagName === "strong" || tagName === "b") return `**${text}**`;
if (tagName === "em" || tagName === "i") return `*${text}*`;
if (tagName === "code") return `\`${text.replace(/`/g, "")}\``;
if (tagName === "a") {
const href = node.getAttribute("href");
return href ? `[${text}](${href})` : text;
}
if (tagName === "li") return text.replace(/\n+/g, " ").trim();
if (tagName === "div" || tagName === "p") return text;
return node.textContent ?? "";
}
export function normalizeEditableMarkdown(root: HTMLElement): string {
const caretOffset = getCaretTextOffset(root);
const markdown = editableHtmlToMarkdown(root);
const html = markdownToEditableHtml(markdown);
if (root.innerHTML !== html) {
root.innerHTML = html;
if (caretOffset !== null) restoreCaretTextOffset(root, caretOffset);
}
return markdown;
}
export function getCaretTextOffset(root: HTMLElement): number | null {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return null;
const range = selection.getRangeAt(0);
if (!root.contains(range.startContainer)) return null;
const prefixRange = document.createRange();
prefixRange.selectNodeContents(root);
prefixRange.setEnd(range.startContainer, range.startOffset);
return prefixRange.toString().length;
}
export function restoreCaretTextOffset(root: HTMLElement, offset: number): void {
const selection = window.getSelection();
if (!selection) return;
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
let remaining = offset;
let node = walker.nextNode();
let lastTextNode: Text | null = null;
while (node) {
const textNode = node as Text;
lastTextNode = textNode;
const length = textNode.textContent?.length ?? 0;
if (remaining <= length) {
const range = document.createRange();
range.setStart(textNode, remaining);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
return;
}
remaining -= length;
node = walker.nextNode();
}
const range = document.createRange();
if (lastTextNode) {
range.setStart(lastTextNode, lastTextNode.textContent?.length ?? 0);
} else {
range.selectNodeContents(root);
}
range.collapse(false);
selection.removeAllRanges();
selection.addRange(range);
}
export function insertPlainText(text: string) {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) return;
selection.deleteFromDocument();
const range = selection.getRangeAt(0);
const textNode = document.createTextNode(text);
range.insertNode(textNode);
range.setStartAfter(textNode);
range.collapse(true);
selection.removeAllRanges();
selection.addRange(range);
}
export function insertParagraphBreak() {
document.execCommand("insertParagraph");
}
function escapeHtml(value: string) {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
function escapeAttribute(value: string) {
return escapeHtml(value).replace(/'/g, "&#39;");
}

View File

@@ -0,0 +1,45 @@
export const ui = {
shell: "h-screen min-w-[1024px] grid grid-rows-[48px_minmax(0,1fr)_52px] bg-[var(--bg)] text-[var(--fg)] text-[13px]",
topbar: "flex items-center gap-3 border-b border-[var(--border)] bg-[var(--surface)] px-5",
wordmark: "border-0 bg-transparent p-0 font-[var(--font-display)] text-[15px] font-semibold leading-none tracking-normal cursor-pointer",
ticker: "bg-[var(--fg)] px-2 py-1 font-[var(--font-mono)] text-[11px] font-bold uppercase tracking-[0.04em] text-[var(--surface)]",
tag: "inline-flex items-center whitespace-nowrap border border-[var(--border)] px-2 py-1 font-[var(--font-mono)] text-[10px] font-medium uppercase tracking-[0.06em] text-[var(--muted)]",
tagAccent: "!border-[var(--accent)] !text-[var(--accent)]",
tagGreen: "!border-[var(--green)] !text-[var(--green)]",
tagRed: "!border-[var(--red)] !text-[var(--red)]",
tab: "border-0 border-r border-[var(--border)] bg-[var(--surface)] px-3.5 py-2 text-xs font-medium text-[var(--muted)] last:border-r-0 hover:text-[var(--fg)]",
tabActive: "!bg-[var(--fg)] !text-[var(--surface)] hover:!text-[var(--surface)]",
iconBtn: "grid h-7 w-7 place-items-center border border-[var(--border)] bg-[var(--surface)] text-[10px] text-[var(--muted)] hover:text-[var(--fg)]",
screen: "h-full min-h-0 overflow-y-auto p-7 pr-9 pl-9",
panel: "bg-[var(--surface)] p-4 min-h-[150px]",
panelTitle: "mb-3 font-[var(--font-mono)] text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--muted)]",
eyebrow: "mb-2 font-[var(--font-mono)] text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--accent)]",
h1: "m-0 mb-2 font-[var(--font-display)] text-[30px] font-bold leading-[1.1] tracking-normal",
h2: "mt-8 mb-3 border-t border-[var(--border)] pt-4 font-[var(--font-display)] text-lg font-semibold leading-tight",
body: "max-w-[760px] text-[13.5px] leading-[1.7]",
muted: "text-[var(--muted)]",
metricGrid: "grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-px border border-[var(--border)] bg-[var(--border)]",
metric: "bg-[var(--surface)] p-3.5",
btn: "border border-[var(--border)] bg-[var(--surface)] px-3.5 py-2 text-xs font-medium text-[var(--fg)] hover:bg-[var(--bg)]",
btnPrimary: "!border-[var(--fg)] !bg-[var(--fg)] !text-[var(--surface)] hover:opacity-90",
btnSm: "!px-2 !py-1 font-[var(--font-mono)] text-[10px]",
nav: "w-60 shrink-0 overflow-y-auto border-r border-[var(--border)] bg-[var(--surface)] p-4",
rightPanel: "w-[300px] shrink-0 overflow-y-auto border-l border-[var(--border)] bg-[var(--surface)] p-5",
center: "min-w-0 flex-1 overflow-y-auto p-8 pl-10 pr-10",
spreadsheet: "w-full border-collapse bg-[var(--surface)] font-[var(--font-mono)] text-xs",
command: "flex items-center justify-between gap-6 border-t border-[var(--border)] bg-[var(--surface)] px-5",
overlay: "fixed inset-0 z-[500] grid place-items-center bg-black/20",
overlayWide: "fixed inset-0 z-[600] grid place-items-center bg-black/20",
toast: "fixed top-14 right-4 z-[1000] flex flex-col gap-2 max-w-[380px] pointer-events-none",
toastItem: "flex items-start gap-2.5 bg-[var(--surface)] border border-[var(--border)] px-3.5 py-3 rounded-sm shadow-[0_4px_12px_oklch(20%_0.02_60_/_0.08)] pointer-events-auto toast-enter",
skeleton: "skeleton",
searchDropdown: "absolute top-full left-0 right-0 bg-[var(--surface)] border border-[var(--border)] border-t-0 shadow-[0_4px_12px_oklch(20%_0.02_60_/_0.08)] z-50 max-h-[400px] overflow-y-auto",
searchRow: "flex items-center gap-3 px-3 py-2.5 text-xs hover:bg-[var(--bg)] cursor-pointer",
confidenceHigh: "border border-[var(--green)] bg-[oklch(95%_0.04_145)] text-[var(--green)] px-1.5 py-0.5 font-[var(--font-mono)] text-[9px] uppercase tracking-wider",
confidenceMed: "border border-[var(--accent)] bg-[oklch(95%_0.04_60)] text-[var(--accent)] px-1.5 py-0.5 font-[var(--font-mono)] text-[9px] uppercase tracking-wider",
confidenceLow: "border border-[var(--red)] bg-[oklch(95%_0.04_25)] text-[var(--red)] px-1.5 py-0.5 font-[var(--font-mono)] text-[9px] uppercase tracking-wider",
validationVerified: "text-[var(--green)]",
validationFlagged: "text-[var(--accent)]",
validationUnverified: "text-[var(--muted)]",
validationFailed: "text-[var(--red)]"
};

10
apps/web/src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./ui/App";
import "./styles.css";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);

12
apps/web/src/rpcClient.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { RpcClient, RpcMethod, RpcRequestMap } from "../../../packages/contracts/src/rpc";
import { handleMockRpc } from "../../../packages/shared/src/mockRpc";
export const rpc: RpcClient = {
call<T extends RpcMethod>(method: T, payload: RpcRequestMap[T]) {
if (!window.mosaic) {
return handleMockRpc(method, payload);
}
return window.mosaic.call(method, payload);
}
};

218
apps/web/src/styles.css Normal file
View File

@@ -0,0 +1,218 @@
@import "tailwindcss";
:root {
--bg: oklch(97% 0.012 80);
--surface: oklch(99% 0.005 80);
--fg: oklch(20% 0.02 60);
--muted: oklch(48% 0.015 60);
--border: oklch(89% 0.012 80);
--accent: oklch(58% 0.16 35);
--green: oklch(52% 0.12 145);
--red: oklch(52% 0.14 25);
--font-display: "Iowan Old Style", Charter, Georgia, serif;
--font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
--font-mono: ui-monospace, "IBM Plex Mono", Menlo, monospace;
--density-body: 15px;
--density-lh: 1.7;
--density-card-pad: 16px;
--density-gap: 1px;
--density-cell-pad: 8px 12px;
--density-metric-min: 140px;
--density-nav-w: 240px;
}
[data-theme="dark"] {
--bg: oklch(15% 0.012 80);
--surface: oklch(18% 0.008 80);
--fg: oklch(92% 0.008 80);
--muted: oklch(60% 0.01 80);
--border: oklch(28% 0.01 80);
--accent: oklch(65% 0.16 35);
--green: oklch(60% 0.12 145);
--red: oklch(60% 0.14 25);
}
[data-density="compact"] {
--density-body: 14px;
--density-lh: 1.5;
--density-card-pad: 12px;
--density-gap: 1px;
--density-cell-pad: 6px 10px;
--density-metric-min: 120px;
--density-nav-w: 220px;
}
[data-density="dense"] {
--density-body: 13px;
--density-lh: 1.4;
--density-card-pad: 8px;
--density-gap: 0px;
--density-cell-pad: 4px 8px;
--density-metric-min: 100px;
--density-nav-w: 200px;
}
html, body, #root { height: 100%; }
body {
margin: 0;
overflow: hidden;
background: var(--bg);
color: var(--fg);
font: calc(var(--density-body) * 1px / var(--density-lh) var(--font-body));
}
button, input, select, textarea { font: inherit; }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
:focus:not(:focus-visible) {
outline: none;
}
/* ─── Memo Editor ─── */
.memo-editor-body:empty::before {
content: attr(data-placeholder);
color: var(--muted);
}
.memo-editor-body p { margin: 0.5rem 0; }
.memo-editor-body h4, .memo-editor-body h5, .memo-editor-body h6 {
margin: 1rem 0 0.5rem;
font-family: var(--font-display);
font-size: 1rem;
font-weight: 600;
line-height: 1.35;
}
.memo-editor-body ul, .memo-editor-body ol { margin: 0.75rem 0; padding-left: 1.25rem; }
.memo-editor-body ul { list-style: disc; }
.memo-editor-body ol { list-style: decimal; }
.memo-editor-body li { margin: 0.25rem 0; }
.memo-editor-body blockquote {
margin: 0.75rem 0;
border-left: 2px solid var(--accent);
padding-left: 0.75rem;
color: var(--muted);
}
.memo-editor-body code {
background: var(--surface);
font-family: var(--font-mono);
font-size: 12px;
padding: 0 0.25rem;
}
.memo-editor-body a {
color: var(--accent);
text-decoration: underline;
}
/* ─── Skeleton Shimmer ─── */
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton {
background: linear-gradient(90deg, oklch(92% 0.008 80) 25%, oklch(96% 0.008 80) 50%, oklch(92% 0.008 80) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s ease-in-out infinite;
border-radius: 2px;
}
[data-theme="dark"] .skeleton {
background: linear-gradient(90deg, oklch(28% 0.01 80) 25%, oklch(22% 0.01 80) 50%, oklch(28% 0.01 80) 75%);
background-size: 200% 100%;
}
/* ─── Screen Transitions ─── */
.screen-transition {
transition: opacity 150ms ease-out;
}
/* ─── Right Panel Slide-in ─── */
.right-panel-slide {
transform: translateX(100%);
transition: transform 200ms ease-out;
}
.right-panel-slide.open {
transform: translateX(0);
}
/* ─── Overlay ─── */
.overlay-backdrop {
opacity: 0;
transition: opacity 150ms ease-out;
}
.overlay-backdrop.open {
opacity: 1;
}
.overlay-body {
transform: scale(0.98);
transition: transform 150ms ease-out;
}
.overlay-backdrop.open .overlay-body {
transform: scale(1);
}
/* ─── Agent Pulse ─── */
@keyframes agent-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.agent-pulse {
animation: agent-pulse 2s ease-in-out infinite;
}
/* ─── Section Flash ─── */
@keyframes section-flash {
0%, 100% { border-left-color: transparent; }
50% { border-left-color: var(--accent); }
}
.section-flash {
animation: section-flash 1s ease-in-out 2;
}
/* ─── Toast ─── */
@keyframes toast-in {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
.toast-enter {
animation: toast-in 200ms ease-out;
}
/* ─── Annotation ─── */
.highlight-annotation {
background: oklch(90% 0.06 80);
border-bottom: 2px solid var(--accent);
cursor: pointer;
}
[data-theme="dark"] .highlight-annotation {
background: oklch(28% 0.03 80);
}
/* ─── Responsive ─── */
@media (max-width: 1023px) {
body > *:not(.floor-message) { display: none !important; }
body::before {
content: '';
display: block;
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: var(--bg);
}
body::after {
content: 'MosaicIQ requires a desktop display of 1024px or wider.';
display: flex;
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
align-items: center; justify-content: center;
font: 400 16px/1.5 var(--font-body);
color: var(--muted); text-align: center;
padding: 40px;
}
}
@media (min-width: 1024px) and (max-width: 1279px) {
.responsive-nav { width: 36px !important; padding: 4px !important; }
.responsive-nav-content { display: none !important; }
.responsive-grid-2 { grid-template-columns: repeat(2, 1fr) !important; }
}
@media (min-width: 1280px) and (max-width: 1439px) {
.responsive-panel { position: absolute; right: 0; top: 0; bottom: 0; z-index: 20; }
}

1002
apps/web/src/ui/App.tsx Normal file

File diff suppressed because it is too large Load Diff

2234
design-doc-v3.html Normal file

File diff suppressed because it is too large Load Diff

12
docs/architecture.md Normal file
View File

@@ -0,0 +1,12 @@
# Architecture
MosaicIQ is split into a small workspace that mirrors the same boundaries used by larger Electron/web monorepos.
- `apps/web`: React and Vite renderer app.
- `apps/desktop`: Electron main process, preload bridge, and local RPC handler.
- `packages/contracts`: shared typed contracts for the renderer/preload/main RPC boundary.
- `packages/shared`: shared runtime data and utilities that are not app-specific.
The renderer never imports Electron directly. It calls the preload bridge through `window.mosaic`, and the Electron main process handles those calls through typed RPC methods from `packages/contracts`.
pnpm is the default package manager for this repo. The committed lockfile is `pnpm-lock.yaml`, and `pnpm-workspace.yaml` owns the Electron/esbuild build-script allowlist needed by pnpm 11.

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

BIN
mosaiciq-home-1024.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
mosaiciq-home-desktop.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

39
package.json Normal file
View File

@@ -0,0 +1,39 @@
{
"name": "mosaiciq",
"version": "0.1.0",
"private": true,
"description": "MosaicIQ local-first equity research workspace.",
"type": "module",
"main": "dist-electron/apps/desktop/src/main.js",
"packageManager": "pnpm@11.0.9",
"workspaces": [
"apps/*",
"packages/*"
],
"scripts": {
"dev": "vite --host 127.0.0.1",
"dev:electron": "concurrently -k \"pnpm run dev\" \"pnpm run desktop:watch\" \"wait-on tcp:5173 dist-electron/apps/desktop/src/main.js && env -u ELECTRON_RUN_AS_NODE electron .\"",
"desktop:watch": "tsc -p tsconfig.electron.json --watch --preserveWatchOutput",
"build": "tsc -b && tsc -p tsconfig.electron.json && vite build",
"preview": "vite preview --host 127.0.0.1",
"typecheck": "tsc --noEmit && tsc -p tsconfig.electron.json --noEmit"
},
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"zod": "^3.24.2"
},
"devDependencies": {
"@tailwindcss/vite": "^4.2.4",
"@types/node": "^22.15.3",
"@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^5.0.0",
"concurrently": "^9.1.2",
"electron": "^36.0.0",
"tailwindcss": "^4.2.4",
"typescript": "^5.8.0",
"vite": "^7.0.0",
"wait-on": "^8.0.3"
}
}

View File

@@ -0,0 +1,11 @@
{
"name": "@mosaiciq/contracts",
"private": true,
"type": "module",
"exports": {
"./rpc": {
"types": "./src/rpc.ts",
"import": "./src/rpc.ts"
}
}
}

View File

@@ -0,0 +1,313 @@
import { z } from "zod";
export const ScreenSchema = z.enum(["home", "workspace", "model", "memo", "agents"]);
export type Screen = z.infer<typeof ScreenSchema>;
export const HoldingSchema = z.object({
ticker: z.string(),
name: z.string(),
price: z.number(),
changePct: z.number(),
weight: z.number()
});
export type Holding = z.infer<typeof HoldingSchema>;
export const AgentSchema = z.object({
id: z.string(),
name: z.string(),
status: z.enum(["idle", "queued", "running", "completed", "paused", "failed"]),
progress: z.number().min(0).max(100),
action: z.string(),
pipeline: z.enum(["research", "competitive", "cross-cutting"]).optional(),
confidence: z.enum(["high", "medium", "low"]).optional()
});
export type Agent = z.infer<typeof AgentSchema>;
export const CompanySchema = z.object({
id: z.string(),
ticker: z.string(),
name: z.string(),
sector: z.string(),
subIndustry: z.string().optional(),
price: z.number(),
changePct: z.number(),
thesis: z.string(),
founded: z.string().optional(),
headquarters: z.string().optional(),
employees: z.number().optional()
});
export type Company = z.infer<typeof CompanySchema>;
export const ModelRowSchema = z.object({
label: z.string(),
kind: z.enum(["actual", "forecast", "total"]),
values: z.array(z.string())
});
export type ModelRow = z.infer<typeof ModelRowSchema>;
export const MemoSectionSchema = z.object({
id: z.string(),
title: z.string(),
content: z.string(),
updatedAt: z.string().optional(),
primaryAgent: z.string().optional()
});
export type MemoSection = z.infer<typeof MemoSectionSchema>;
export const MemoCitationSchema = z.object({
id: z.string(),
label: z.string(),
sectionId: z.string(),
type: z.enum(["sec_filing", "earnings_transcript", "analyst_report", "model", "internal_note"]),
title: z.string(),
reference: z.string(),
verificationStatus: z.enum(["verified", "unverified", "flagged"]),
sourceUrl: z.string().optional()
});
export type MemoCitation = z.infer<typeof MemoCitationSchema>;
export const MemoAnnotationSchema = z.object({
id: z.string(),
sectionId: z.string(),
kind: z.enum(["highlight", "comment", "strike"]),
selectedText: z.string(),
comment: z.string().optional(),
createdBy: z.string(),
createdAt: z.string(),
status: z.enum(["open", "resolved"])
});
export type MemoAnnotation = z.infer<typeof MemoAnnotationSchema>;
export const MemoSectionReviewSchema = z.object({
sectionId: z.string(),
status: z.enum(["pending", "in_review", "approved", "changes_requested"]),
updatedAt: z.string().optional()
});
export type MemoSectionReview = z.infer<typeof MemoSectionReviewSchema>;
export const CatalystSchema = z.object({
id: z.string(),
date: z.string(),
event: z.string(),
impact: z.enum(["high", "medium", "low"]),
thesisRelevance: z.enum(["supports", "challenges", "neutral"]),
source: z.string().optional()
});
export type Catalyst = z.infer<typeof CatalystSchema>;
export const AlertSchema = z.object({
id: z.string(),
companyId: z.string().optional(),
timestamp: z.string(),
type: z.enum(["filing", "price_move", "earnings_surprise", "peer_event"]),
description: z.string(),
thesisImpact: z.enum(["positive", "negative", "neutral"]),
status: z.enum(["new", "reviewed"]),
targetSection: z.string().optional()
});
export type Alert = z.infer<typeof AlertSchema>;
export const RiskSchema = z.object({
id: z.string(),
companyId: z.string(),
risk: z.string(),
category: z.enum(["business", "financial", "competitive", "regulatory", "esg"]),
severity: z.enum(["high", "medium", "low"]),
likelihood: z.enum(["high", "medium", "low"]),
mitigation: z.string(),
status: z.enum(["open", "mitigated", "accepted"])
});
export type Risk = z.infer<typeof RiskSchema>;
export const EarningsScheduleSchema = z.object({
id: z.string(),
companyId: z.string(),
quarter: z.string(),
expectedDate: z.string(),
timing: z.enum(["bmo", "amc"]).optional(),
actualRevenue: z.string().optional(),
expectedRevenue: z.string().optional(),
actualEps: z.string().optional(),
expectedEps: z.string().optional()
});
export type EarningsSchedule = z.infer<typeof EarningsScheduleSchema>;
export const FilingSchema = z.object({
id: z.string(),
companyId: z.string(),
formType: z.string(),
filedDate: z.string(),
title: z.string(),
keyChanges: z.string().optional(),
reviewed: z.boolean().optional()
});
export type Filing = z.infer<typeof FilingSchema>;
export const ExportRecordSchema = z.object({
id: z.string(),
type: z.enum(["pdf", "excel", "ppt"]),
title: z.string(),
companyId: z.string().optional(),
format: z.string(),
fileSize: z.string().optional(),
status: z.enum(["processing", "complete", "failed"]),
createdAt: z.string(),
downloadUrl: z.string().optional()
});
export type ExportRecord = z.infer<typeof ExportRecordSchema>;
export const WorkspaceSectionSchema = z.object({
id: z.string(),
title: z.string(),
content: z.string(),
validationState: z.enum(["verified", "flagged", "unverified", "failed"]),
sourceAgent: z.string().optional()
});
export type WorkspaceSection = z.infer<typeof WorkspaceSectionSchema>;
export const SnapshotSchema = z.object({
id: z.string(),
timestamp: z.string(),
label: z.string().optional(),
type: z.enum(["auto", "manual"]),
changeCount: z.number()
});
export type Snapshot = z.infer<typeof SnapshotSchema>;
export type ClientSettings = {
theme: "light" | "dark" | "system";
density: "comfortable" | "compact" | "dense";
sidebarWidth: number;
navCollapsed: Record<string, boolean>;
keybindings: Record<string, string>;
};
export type ServerSettings = {
agentConfigs: Record<string, unknown>;
dataSources: Record<string, boolean>;
exportPipelines: Record<string, unknown>;
};
export type RpcRequestMap = {
"portfolio.get": undefined;
"portfolio.addHolding": { ticker: string };
"portfolio.removeHolding": { ticker: string };
"company.get": { companyId: string };
"company.search": { query: string };
"company.setActive": { companyId: string };
"workspace.getSection": { companyId: string; section: string };
"workspace.listSources": { companyId: string };
"catalyst.list": { companyId: string };
"alert.list": { companyId?: string; since?: string };
"risk.list": { companyId: string };
"risk.add": { companyId: string; risk: Omit<Risk, "id" | "companyId"> };
"earnings.getSchedule": { companyId: string };
"filing.list": { companyId: string; since?: string };
"model.get": { companyId: string; tab: string };
"model.updateCell": { companyId: string; tab: string; row: number; col: number; value: string };
"model.runScenario": { companyId: string; scenario: string; overrides: Record<string, string> };
"memo.get": { companyId: string };
"memo.updateSection": {
companyId: string;
sectionId: string;
title?: string;
content: string;
};
"memo.addAnnotation": {
companyId: string;
sectionId: string;
kind: "highlight" | "comment" | "strike";
selectedText: string;
comment?: string;
};
"memo.resolveAnnotation": {
companyId: string;
annotationId: string;
};
"memo.updateSectionReview": {
companyId: string;
sectionId: string;
status: "pending" | "in_review" | "approved" | "changes_requested";
};
"memo.acceptEdit": { companyId: string; editId: string };
"memo.rejectEdit": { companyId: string; editId: string; reason?: string };
"agent.list": { companyId?: string };
"agent.start": { agentId: string; companyId: string };
"agent.pause": { agentId: string };
"agent.restart": { agentId: string };
"agent.chat": { agentId: string; message: string };
"agent.configure": { agentId: string; config: Record<string, unknown> };
"agent.getTrace": { agentId: string; runId: string };
"agent.runPipeline": { companyId: string; pipeline: string };
"export.list": { companyId?: string };
"export.create": { type: string; companyId: string; options?: Record<string, unknown> };
"export.download": { exportId: string };
"settings.get": { scope: "client" | "server" };
"settings.update": { scope: "client" | "server"; changes: Record<string, unknown> };
};
export type RpcResponseMap = {
"portfolio.get": { id: string; name: string; holdings: Holding[]; activeCompanyId: string };
"portfolio.addHolding": { holding: Holding };
"portfolio.removeHolding": { ok: boolean };
"company.get": { company: Company };
"company.search": { results: Array<{ ticker: string; name: string; sector: string }> };
"company.setActive": { ok: boolean };
"workspace.getSection": { content: WorkspaceSection; validationState: string };
"workspace.listSources": { sources: Array<{ type: string; title: string; metadata: string }> };
"catalyst.list": { catalysts: Catalyst[] };
"alert.list": { alerts: Alert[] };
"risk.list": { risks: Risk[] };
"risk.add": { risk: Risk };
"earnings.getSchedule": { schedule: EarningsSchedule[] };
"filing.list": { filings: Filing[] };
"model.get": { headers: string[]; rows: ModelRow[] };
"model.updateCell": { ok: boolean; affectedCells: string[] };
"model.runScenario": { headers: string[]; rows: ModelRow[] };
"memo.get": {
status: "draft" | "review" | "final";
sections: MemoSection[];
citations: MemoCitation[];
annotations: MemoAnnotation[];
sectionReviews: MemoSectionReview[];
};
"memo.updateSection": {
section: MemoSection;
status: "draft" | "review" | "final";
savedAt: string;
};
"memo.addAnnotation": { annotation: MemoAnnotation };
"memo.resolveAnnotation": { annotation: MemoAnnotation };
"memo.updateSectionReview": { review: MemoSectionReview };
"memo.acceptEdit": { ok: boolean };
"memo.rejectEdit": { ok: boolean };
"agent.list": { agents: Agent[] };
"agent.start": { runId: string };
"agent.pause": { ok: boolean };
"agent.restart": { runId: string };
"agent.chat": { response: string };
"agent.configure": { ok: boolean };
"agent.getTrace": { steps: Array<{ step: number; label: string; detail: string }> };
"agent.runPipeline": { runIds: string[] };
"export.list": { exports: ExportRecord[] };
"export.create": { exportId: string };
"export.download": { data: ArrayBuffer };
"settings.get": { settings: ClientSettings | ServerSettings };
"settings.update": { ok: boolean };
};
export type RpcMethod = keyof RpcRequestMap;
export type RpcError = {
code: "NOT_FOUND" | "VALIDATION_ERROR" | "INTERNAL_ERROR" | "AGENT_FAILED" | "CONFLICT" | "RATE_LIMITED";
message: string;
detail?: unknown;
};
export type RpcResult<T extends RpcMethod> =
| { ok: true; data: RpcResponseMap[T] }
| { ok: false; error: RpcError };
export type RpcClient = {
call<T extends RpcMethod>(method: T, payload: RpcRequestMap[T]): Promise<RpcResult<T>>;
};

View File

@@ -0,0 +1,19 @@
{
"name": "@mosaiciq/shared",
"private": true,
"type": "module",
"exports": {
"./demoData": {
"types": "./src/demoData.ts",
"import": "./src/demoData.ts"
},
"./extraData": {
"types": "./src/extraData.ts",
"import": "./src/extraData.ts"
},
"./mockRpc": {
"types": "./src/mockRpc.ts",
"import": "./src/mockRpc.ts"
}
}
}

View File

@@ -0,0 +1,162 @@
import type { Agent, Company, Holding, MemoAnnotation, MemoCitation, MemoSection, MemoSectionReview, ModelRow } from "../../contracts/src/rpc.js";
export const activeCompanyId = "cost";
export const holdings: Holding[] = [
{ ticker: "COST", name: "Costco Wholesale Corp", price: 921.4, changePct: 1.2, weight: 32 },
{ ticker: "AMZN", name: "Amazon.com Inc", price: 186.5, changePct: 0.8, weight: 28 },
{ ticker: "WMT", name: "Walmart Inc", price: 168.3, changePct: -0.3, weight: 22 },
{ ticker: "TGT", name: "Target Corp", price: 142.8, changePct: -1.1, weight: 18 }
];
export const companies: Record<string, Company> = {
cost: {
id: "cost",
ticker: "COST",
name: "Costco Wholesale Corporation",
sector: "Consumer Staples",
subIndustry: "Membership Warehouse Clubs",
price: 921.4,
changePct: 1.2,
thesis: "Membership renewal durability and traffic resilience support premium multiple, while fuel normalization and wage pressure remain the key model sensitivities.",
founded: "1983",
headquarters: "Issaquah, WA",
employees: 320000
},
amzn: {
id: "amzn",
ticker: "AMZN",
name: "Amazon.com Inc",
sector: "Consumer Discretionary",
subIndustry: "Internet Retail",
price: 186.5,
changePct: 0.8,
thesis: "AWS re-acceleration and advertising monetization drive margin expansion, while retail competition and regulatory risk remain.",
founded: "1994",
headquarters: "Seattle, WA",
employees: 1540000
},
wmt: {
id: "wmt",
ticker: "WMT",
name: "Walmart Inc",
sector: "Consumer Staples",
subIndustry: "Hypermarkets",
price: 168.3,
changePct: -0.3,
thesis: "Grocery share gains and e-commerce profitability inflection offset margin pressure from price investment strategy.",
founded: "1962",
headquarters: "Bentonville, AR",
employees: 2100000
},
tgt: {
id: "tgt",
ticker: "TGT",
name: "Target Corp",
sector: "Consumer Discretionary",
subIndustry: "General Merchandise Stores",
price: 142.8,
changePct: -1.1,
thesis: "Traffic recovery and margin normalization potential, but discretionary headwinds and inventory risk weigh on near-term.",
founded: "1902",
headquarters: "Minneapolis, MN",
employees: 415000
}
};
export const agents: Agent[] = [
{ id: "sf", name: "SEC Filings Agent", status: "running", progress: 45, action: "Extracting segment data from 10-K", pipeline: "research" },
{ id: "fm", name: "Financial Modeling Agent", status: "running", progress: 62, action: "Building revenue schedule", pipeline: "research" },
{ id: "ec", name: "Earnings Call Agent", status: "running", progress: 78, action: "Summarizing Q2 FY25 call", pipeline: "competitive" },
{ id: "cr", name: "Company Research Agent", status: "completed", progress: 100, action: "Company overview complete", pipeline: "research" },
{ id: "va", name: "Valuation Agent", status: "queued", progress: 0, action: "Waiting for model outputs", pipeline: "research" },
{ id: "ci", name: "Competitive Intel Agent", status: "queued", progress: 0, action: "Waiting for earnings analysis", pipeline: "competitive" },
{ id: "rk", name: "Risk Agent", status: "idle", progress: 0, action: "Risk analysis pending", pipeline: "competitive" },
{ id: "mw", name: "Memo Writing Agent", status: "idle", progress: 0, action: "Memo drafting pending", pipeline: "research" },
{ id: "pa", name: "Presentation Agent", status: "idle", progress: 0, action: "Presentation pending", pipeline: "research" },
{ id: "mn", name: "Monitoring Agent", status: "idle", progress: 0, action: "Monitoring pipeline idle", pipeline: "cross-cutting" },
{ id: "sv", name: "Source Verification Agent", status: "idle", progress: 0, action: "Verification pending", pipeline: "cross-cutting" },
{ id: "rt", name: "Red Team Agent", status: "idle", progress: 0, action: "Adversarial review pending", pipeline: "competitive" },
{ id: "ex", name: "Export Agent", status: "idle", progress: 0, action: "Export pipeline idle", pipeline: "cross-cutting" },
{ id: "qa", name: "Model QA Agent", status: "idle", progress: 0, action: "QA review pending", pipeline: "cross-cutting" }
];
export const modelHeaders = ["FY2022A", "FY2023A", "FY2024A", "FY2025E", "FY2026E"];
export const modelRows: ModelRow[] = [
{ label: "Revenue", kind: "actual", values: ["$226.9B", "$242.3B", "$254.5B", "$270.4B", "$286.1B"] },
{ label: "Gross Margin", kind: "actual", values: ["12.1%", "12.3%", "12.6%", "12.7%", "12.8%"] },
{ label: "Operating Income", kind: "forecast", values: ["$7.8B", "$8.1B", "$9.3B", "$10.1B", "$10.9B"] },
{ label: "EPS", kind: "forecast", values: ["$13.14", "$14.16", "$16.56", "$18.12", "$19.84"] }
];
export const memoSections: MemoSection[] = [
{
id: "thesis",
title: "Investment Thesis",
content: "Costco remains a high-quality compounder with unusually durable traffic, renewal, and private-label economics. The core thesis is that membership fee income, disciplined SKU curation, and steady warehouse productivity can support high-single-digit earnings growth over a multi-year horizon, even as the current valuation requires disciplined sensitivity work around renewal fees, wage inflation, and merchandise margin.",
primaryAgent: "mw"
},
{
id: "drivers",
title: "Key Drivers",
content: "The model is most sensitive to membership fee cadence, comparable sales excluding fuel, and operating leverage across warehouse labor and logistics. A 50 bps change in core merchandise margin or a one-year shift in fee timing drives a disproportionate share of the bear-to-bull spread.",
primaryAgent: "mw"
},
{
id: "variant",
title: "Variant Perception",
content: "Consensus treats Costco as a fully discovered quality compounder, but underweights the durability of traffic share gains in grocery and consumables. The variant view is that renewal behavior and executive member penetration create more operating resilience than the market is giving credit for during a slower discretionary cycle.",
primaryAgent: "mw"
},
{
id: "valuation",
title: "Valuation",
content: "The base case triangulates a premium earnings multiple, a DCF anchored on low-teens discount-rate sensitivity, and peer multiples against scaled staples and retail platforms. The current share price embeds limited margin for execution misses, so valuation work should frame upside through fee timing and downside through wage and shrink pressure.",
primaryAgent: "va"
},
{
id: "quality",
title: "Business Quality",
content: "Costco's moat is built on purchasing scale, a low-markup operating philosophy, a limited-SKU model, and recurring membership economics. ROIC remains supported by high inventory turns and negative working capital dynamics, while management quality is reflected in disciplined capital allocation and consistent reinvestment in member value.",
primaryAgent: "cr"
},
{
id: "financials",
title: "Financial Summary",
content: "Revenue growth is expected to track warehouse expansion, comparable sales excluding fuel, and modest e-commerce contribution. Margin analysis should separate merchandise gross margin, membership fee income, wage inflation, logistics costs, and fuel volatility so the model does not overstate operating leverage.",
primaryAgent: "fm"
},
{
id: "risks",
title: "Risks & Mitigants",
content: "Key risks include valuation compression, delayed fee increases, labor cost inflation, weaker discretionary categories, and international execution risk. Mitigants include renewal-rate stability, grocery-led traffic, balance-sheet flexibility, and management's demonstrated willingness to protect the member value proposition through cycles.",
primaryAgent: "rk"
},
{
id: "catalysts",
title: "Catalysts",
content: "Near-term catalysts include membership fee announcements, monthly sales reports, executive member penetration updates, new warehouse openings, and quarterly commentary on traffic versus ticket. A clean fee-increase signal with stable renewal metrics would likely be the most important rerating event.",
primaryAgent: "mn"
}
];
export const memoCitations: MemoCitation[] = [
{ id: "citation-10k-membership", label: "[1]", sectionId: "thesis", type: "sec_filing", title: "FY2024 10-K - Membership economics", reference: "Annual report discussion of membership fee income and renewal rates", verificationStatus: "verified", sourceUrl: "https://investor.costco.com" },
{ id: "citation-call-executive", label: "[2]", sectionId: "drivers", type: "earnings_transcript", title: "Q2 FY2025 earnings call - Executive tier metrics", reference: "Management commentary on executive member penetration and traffic", verificationStatus: "verified" },
{ id: "citation-consensus-margin", label: "[3]", sectionId: "variant", type: "analyst_report", title: "Consensus margin sensitivity note", reference: "External analyst framing of merchandise margin risk", verificationStatus: "unverified" },
{ id: "citation-dcf-model", label: "[4]", sectionId: "valuation", type: "model", title: "Internal DCF sensitivity model", reference: "Discount-rate and terminal multiple sensitivity output", verificationStatus: "flagged" },
{ id: "citation-risk-note", label: "[5]", sectionId: "risks", type: "internal_note", title: "Risk register - Labor and fee timing", reference: "Internal notes from source verification pass", verificationStatus: "verified" }
];
export const memoAnnotations: MemoAnnotation[] = [
{ id: "annotation-thesis-target", sectionId: "thesis", kind: "comment", selectedText: "current valuation requires disciplined sensitivity work", comment: "Quantify target price range before IC circulation.", createdBy: "JD", createdAt: "2026-05-09T14:30:00.000Z", status: "open" },
{ id: "annotation-drivers-fee", sectionId: "drivers", kind: "comment", selectedText: "membership fee cadence", comment: "Tie fee cadence to the model sensitivity table.", createdBy: "MW", createdAt: "2026-05-09T15:10:00.000Z", status: "open" },
{ id: "annotation-valuation-dcf", sectionId: "valuation", kind: "highlight", selectedText: "low-teens discount-rate sensitivity", createdBy: "SV", createdAt: "2026-05-09T16:05:00.000Z", status: "open" }
];
export const memoSectionReviews: MemoSectionReview[] = memoSections.map((section) => ({
sectionId: section.id,
status: section.id === "thesis" ? "approved" : section.id === "drivers" || section.id === "variant" ? "in_review" : section.id === "valuation" ? "changes_requested" : "pending",
updatedAt: "2026-05-09T13:00:00.000Z"
}));

View File

@@ -0,0 +1,44 @@
import type { Alert, Catalyst, EarningsSchedule, ExportRecord, Filing, Risk } from "../../contracts/src/rpc.js";
export const demoCatalysts: Catalyst[] = [
{ id: "cat-1", date: "2026-06-05", event: "Q3 FY25 Earnings", impact: "high", thesisRelevance: "supports", source: "[4]" },
{ id: "cat-2", date: "2026-07-15", event: "Executive member update", impact: "medium", thesisRelevance: "supports", source: "[2]" },
{ id: "cat-3", date: "2026-08-01", event: "Annual membership fee review", impact: "high", thesisRelevance: "neutral", source: "[1]" },
{ id: "cat-4", date: "2026-09-10", event: "New warehouse openings (Q4)", impact: "low", thesisRelevance: "supports", source: "[1]" }
];
export const demoAlerts: Alert[] = [
{ id: "alert-1", companyId: "cost", timestamp: "2026-05-12T08:30:00Z", type: "earnings_surprise", description: "Q2 FY25 earnings beat (+7.5% comp)", thesisImpact: "positive", status: "new", targetSection: "thesis" },
{ id: "alert-2", companyId: "cost", timestamp: "2026-05-12T06:00:00Z", type: "filing", description: "New 8-K: Executive compensation update", thesisImpact: "neutral", status: "new" },
{ id: "alert-3", companyId: "wmt", timestamp: "2026-05-11T14:00:00Z", type: "peer_event", description: "WMT announces price investment in grocery", thesisImpact: "negative", status: "reviewed" },
{ id: "alert-4", companyId: "cost", timestamp: "2026-05-11T10:00:00Z", type: "price_move", description: "COST +2.1% on heavy volume", thesisImpact: "positive", status: "reviewed" }
];
export const demoRisks: Risk[] = [
{ id: "risk-1", companyId: "cost", risk: "Amazon enters warehouse club segment", category: "competitive", severity: "high", likelihood: "low", mitigation: "Costco's 93% renewal rate creates switching costs", status: "open" },
{ id: "risk-2", companyId: "cost", risk: "Wage inflation compresses operating margin", category: "financial", severity: "medium", likelihood: "high", mitigation: "Automation and productivity offset 40-60% of wage pressure", status: "open" },
{ id: "risk-3", companyId: "cost", risk: "Membership fee increase delayed beyond FY26", category: "financial", severity: "medium", likelihood: "medium", mitigation: "Fee income growth from executive tier conversion", status: "mitigated" },
{ id: "risk-4", companyId: "cost", risk: "Regulatory pressure on merchandise sourcing", category: "regulatory", severity: "low", likelihood: "low", mitigation: "Diversified supply chain across 14 countries", status: "accepted" },
{ id: "risk-5", companyId: "cost", risk: "E-commerce disruption of warehouse model", category: "competitive", severity: "medium", likelihood: "medium", mitigation: "Costco.com growth and Instacart partnership", status: "open" },
{ id: "risk-6", companyId: "cost", risk: "Valuation compression on growth deceleration", category: "financial", severity: "high", likelihood: "medium", mitigation: "High-single-digit earnings growth supports premium", status: "open" }
];
export const demoEarnings: EarningsSchedule[] = [
{ id: "earn-1", companyId: "cost", quarter: "Q3 FY25", expectedDate: "2026-06-05", timing: "bmo" },
{ id: "earn-2", companyId: "cost", quarter: "Q4 FY25", expectedDate: "2026-09-25", timing: "bmo" },
{ id: "earn-3", companyId: "cost", quarter: "Q2 FY25", expectedDate: "2026-03-06", timing: "bmo", actualRevenue: "$62.5B", expectedRevenue: "$61.2B", actualEps: "$4.28", expectedEps: "$4.05" },
{ id: "earn-4", companyId: "cost", quarter: "Q1 FY25", expectedDate: "2025-12-12", timing: "bmo", actualRevenue: "$60.2B", expectedRevenue: "$59.8B", actualEps: "$4.04", expectedEps: "$3.98" }
];
export const demoFilings: Filing[] = [
{ id: "filing-1", companyId: "cost", formType: "10-K", filedDate: "2024-10-10", title: "Annual Report FY2024", keyChanges: "Updated segment reporting, new warehouse commitments ($2.1B)", reviewed: true },
{ id: "filing-2", companyId: "cost", formType: "10-Q", filedDate: "2026-03-10", title: "Quarterly Report Q2 FY25", keyChanges: "Membership fee income growth, margin expansion", reviewed: true },
{ id: "filing-3", companyId: "cost", formType: "8-K", filedDate: "2026-05-12", title: "Executive Compensation Update", keyChanges: "New CEO compensation structure", reviewed: false }
];
export const demoExports: ExportRecord[] = [
{ id: "export-1", type: "excel", title: "COST Model — Revenue Build FY25-FY27", companyId: "cost", format: "Excel", fileSize: "2.4 MB", status: "complete", createdAt: "2026-05-10T14:30:00Z" },
{ id: "export-2", type: "pdf", title: "COST Investment Memo — Draft", companyId: "cost", format: "PDF", fileSize: "1.8 MB", status: "complete", createdAt: "2026-05-09T16:00:00Z" },
{ id: "export-3", type: "ppt", title: "COST IC Presentation", companyId: "cost", format: "PowerPoint", fileSize: "4.2 MB", status: "processing", createdAt: "2026-05-12T09:00:00Z" },
{ id: "export-4", type: "pdf", title: "Peer Comparison Report", companyId: "cost", format: "PDF", fileSize: "980 KB", status: "complete", createdAt: "2026-05-08T11:00:00Z" }
];

View File

@@ -0,0 +1,242 @@
import type { MemoAnnotation, MemoCitation, MemoSection, MemoSectionReview, RpcMethod, RpcRequestMap, RpcResponseMap, RpcResult } from "../../contracts/src/rpc.js";
import { activeCompanyId, agents, companies, holdings, memoAnnotations, memoCitations, memoSectionReviews, memoSections, modelHeaders, modelRows } from "./demoData.js";
import { demoAlerts, demoCatalysts, demoEarnings, demoExports, demoFilings, demoRisks } from "./extraData.js";
type MemoStoreEntry = {
status: "draft" | "review" | "final";
sections: MemoSection[];
citations: MemoCitation[];
annotations: MemoAnnotation[];
sectionReviews: MemoSectionReview[];
};
const memoStore = new Map<string, MemoStoreEntry>();
export async function handleMockRpc<T extends RpcMethod>(
method: T,
payload: RpcRequestMap[T]
): Promise<RpcResult<T>> {
try {
switch (method) {
case "portfolio.get":
return ok("portfolio.get", { id: "core", name: "Core Retail Coverage", holdings, activeCompanyId }) as RpcResult<T>;
case "portfolio.addHolding": {
const { ticker } = payload as RpcRequestMap["portfolio.addHolding"];
const company = Object.values(companies).find((c) => c.ticker === ticker.toUpperCase());
if (!company) return fail("NOT_FOUND", `Company with ticker "${ticker}" not found.`) as RpcResult<T>;
const holding = { ticker: company.ticker, name: company.name, price: company.price, changePct: company.changePct, weight: 5 };
return ok("portfolio.addHolding", { holding }) as RpcResult<T>;
}
case "portfolio.removeHolding":
return ok("portfolio.removeHolding", { ok: true }) as RpcResult<T>;
case "company.get": {
const { companyId } = payload as RpcRequestMap["company.get"];
const company = companies[companyId];
if (!company) return fail("NOT_FOUND", `Company "${companyId}" was not found.`) as RpcResult<T>;
return ok("company.get", { company }) as RpcResult<T>;
}
case "company.search": {
const { query } = payload as RpcRequestMap["company.search"];
const q = query.toLowerCase();
const results = Object.values(companies).filter((c) => c.ticker.toLowerCase().includes(q) || c.name.toLowerCase().includes(q) || c.sector.toLowerCase().includes(q)).map((c) => ({ ticker: c.ticker, name: c.name, sector: c.sector }));
return ok("company.search", { results }) as RpcResult<T>;
}
case "company.setActive":
return ok("company.setActive", { ok: true }) as RpcResult<T>;
case "workspace.getSection": {
const { companyId, section } = payload as RpcRequestMap["workspace.getSection"];
if (!companies[companyId]) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
return ok("workspace.getSection", { content: { id: section, title: section, content: `Content for ${section}`, validationState: "verified" as const, sourceAgent: "cr" }, validationState: "verified" }) as RpcResult<T>;
}
case "workspace.listSources": {
const { companyId } = payload as RpcRequestMap["workspace.listSources"];
if (!companies[companyId]) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
return ok("workspace.listSources", { sources: [{ type: "SEC Filing", title: "10-K FY2024", metadata: "Filed Oct 2024" }, { type: "Earnings Transcript", title: "Q2 FY25 Call", metadata: "Mar 2025" }] }) as RpcResult<T>;
}
case "catalyst.list":
return ok("catalyst.list", { catalysts: demoCatalysts }) as RpcResult<T>;
case "alert.list":
return ok("alert.list", { alerts: demoAlerts }) as RpcResult<T>;
case "risk.list":
return ok("risk.list", { risks: demoRisks }) as RpcResult<T>;
case "risk.add": {
const { companyId, risk } = payload as RpcRequestMap["risk.add"];
const newRisk = { ...risk, id: `risk-${Date.now()}`, companyId };
return ok("risk.add", { risk: newRisk }) as RpcResult<T>;
}
case "earnings.getSchedule":
return ok("earnings.getSchedule", { schedule: demoEarnings }) as RpcResult<T>;
case "filing.list":
return ok("filing.list", { filings: demoFilings }) as RpcResult<T>;
case "agent.list":
return ok("agent.list", { agents }) as RpcResult<T>;
case "agent.start":
return ok("agent.start", { runId: `run-${Date.now()}` }) as RpcResult<T>;
case "agent.pause":
return ok("agent.pause", { ok: true }) as RpcResult<T>;
case "agent.restart":
return ok("agent.restart", { runId: `run-${Date.now()}` }) as RpcResult<T>;
case "agent.chat":
return ok("agent.chat", { response: "I've analyzed the data. Key findings: revenue growth remains on track with 5.6% YoY. The main assumption driving the model is membership fee cadence." }) as RpcResult<T>;
case "agent.configure":
return ok("agent.configure", { ok: true }) as RpcResult<T>;
case "agent.getTrace":
return ok("agent.getTrace", { steps: [{ step: 1, label: "Load filings", detail: "Loaded 10-K and 3 quarterly reports" }, { step: 2, label: "Extract segments", detail: "Parsed 5 revenue segments" }, { step: 3, label: "Build model", detail: "Constructed revenue build with growth rates" }] }) as RpcResult<T>;
case "agent.runPipeline":
return ok("agent.runPipeline", { runIds: [`run-${Date.now()}`, `run-${Date.now() + 1}`, `run-${Date.now() + 2}`] }) as RpcResult<T>;
case "model.get":
return ok("model.get", { headers: modelHeaders, rows: modelRows }) as RpcResult<T>;
case "model.updateCell":
return ok("model.updateCell", { ok: true, affectedCells: [] }) as RpcResult<T>;
case "model.runScenario":
return ok("model.runScenario", { headers: [...modelHeaders], rows: modelRows.map((r) => ({ ...r })) }) as RpcResult<T>;
case "memo.get": {
const { companyId } = payload as RpcRequestMap["memo.get"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" was not found.`) as RpcResult<T>;
return ok("memo.get", cloneMemo(memo)) as RpcResult<T>;
}
case "memo.updateSection": {
const { companyId, sectionId, title, content } = payload as RpcRequestMap["memo.updateSection"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
if (content.trim().length === 0) return fail("VALIDATION_ERROR", "Memo section content cannot be empty.") as RpcResult<T>;
if (title !== undefined && title.trim().length === 0) return fail("VALIDATION_ERROR", "Memo section title cannot be empty.") as RpcResult<T>;
const idx = memo.sections.findIndex((s) => s.id === sectionId);
if (idx === -1) return fail("NOT_FOUND", `Section "${sectionId}" not found.`) as RpcResult<T>;
const savedAt = new Date().toISOString();
const section = { ...memo.sections[idx], ...(title === undefined ? {} : { title }), content, updatedAt: savedAt };
memo.sections[idx] = section;
return ok("memo.updateSection", { section: { ...section }, status: memo.status, savedAt }) as RpcResult<T>;
}
case "memo.addAnnotation": {
const { companyId, sectionId, kind, selectedText, comment } = payload as RpcRequestMap["memo.addAnnotation"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
if (!memo.sections.some((s) => s.id === sectionId)) return fail("NOT_FOUND", `Section "${sectionId}" not found.`) as RpcResult<T>;
if (selectedText.trim().length === 0) return fail("VALIDATION_ERROR", "Annotation selected text cannot be empty.") as RpcResult<T>;
if (kind === "comment" && (!comment || comment.trim().length === 0)) return fail("VALIDATION_ERROR", "Comment annotation requires comment text.") as RpcResult<T>;
const annotation: MemoAnnotation = { id: `annotation-${Date.now()}`, sectionId, kind, selectedText: selectedText.trim(), ...(comment === undefined ? {} : { comment: comment.trim() }), createdBy: "JD", createdAt: new Date().toISOString(), status: "open" };
memo.annotations.unshift(annotation);
return ok("memo.addAnnotation", { annotation: { ...annotation } }) as RpcResult<T>;
}
case "memo.resolveAnnotation": {
const { companyId, annotationId } = payload as RpcRequestMap["memo.resolveAnnotation"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
const ai = memo.annotations.findIndex((a) => a.id === annotationId);
if (ai === -1) return fail("NOT_FOUND", `Annotation "${annotationId}" not found.`) as RpcResult<T>;
const annotation = { ...memo.annotations[ai], status: "resolved" as const };
memo.annotations[ai] = annotation;
return ok("memo.resolveAnnotation", { annotation: { ...annotation } }) as RpcResult<T>;
}
case "memo.updateSectionReview": {
const { companyId, sectionId, status } = payload as RpcRequestMap["memo.updateSectionReview"];
const memo = getCompanyMemo(companyId);
if (!memo) return fail("NOT_FOUND", `Company "${companyId}" not found.`) as RpcResult<T>;
if (!memo.sections.some((s) => s.id === sectionId)) return fail("NOT_FOUND", `Section "${sectionId}" not found.`) as RpcResult<T>;
const updatedAt = new Date().toISOString();
const review = { sectionId, status, updatedAt };
const ri = memo.sectionReviews.findIndex((r) => r.sectionId === sectionId);
if (ri === -1) memo.sectionReviews.push(review);
else memo.sectionReviews[ri] = review;
return ok("memo.updateSectionReview", { review: { ...review } }) as RpcResult<T>;
}
case "memo.acceptEdit":
return ok("memo.acceptEdit", { ok: true }) as RpcResult<T>;
case "memo.rejectEdit":
return ok("memo.rejectEdit", { ok: true }) as RpcResult<T>;
case "export.list":
return ok("export.list", { exports: demoExports }) as RpcResult<T>;
case "export.create":
return ok("export.create", { exportId: `export-${Date.now()}` }) as RpcResult<T>;
case "export.download":
return ok("export.download", { data: new ArrayBuffer(0) }) as RpcResult<T>;
case "settings.get": {
const { scope } = payload as RpcRequestMap["settings.get"];
if (scope === "client") return ok("settings.get", { settings: { theme: "light" as const, density: "comfortable" as const, sidebarWidth: 240, navCollapsed: {}, keybindings: {} } }) as RpcResult<T>;
return ok("settings.get", { settings: { agentConfigs: {}, dataSources: { sec_filings: true, transcripts: true, market_data: false, analyst_reports: false, press_releases: true }, exportPipelines: {} } }) as RpcResult<T>;
}
case "settings.update":
return ok("settings.update", { ok: true }) as RpcResult<T>;
default:
return fail("NOT_FOUND", `Unknown RPC method: ${String(method)}`) as RpcResult<T>;
}
} catch (error) {
return fail("INTERNAL_ERROR", "Unhandled mock RPC failure.", error) as RpcResult<T>;
}
}
function ok<T extends RpcMethod>(_: T, data: RpcResponseMap[T]): RpcResult<T> {
return { ok: true, data };
}
function getCompanyMemo(companyId: string): MemoStoreEntry | null {
if (!companies[companyId]) return null;
const stored = memoStore.get(companyId);
if (stored) return stored;
const memo = {
status: "draft" as const,
sections: memoSections.map((s) => ({ ...s })),
citations: memoCitations.map((c) => ({ ...c })),
annotations: memoAnnotations.map((a) => ({ ...a })),
sectionReviews: memoSectionReviews.map((r) => ({ ...r }))
};
memoStore.set(companyId, memo);
return memo;
}
function cloneMemo(memo: MemoStoreEntry): RpcResponseMap["memo.get"] {
return {
status: memo.status,
sections: memo.sections.map((s) => ({ ...s })),
citations: memo.citations.map((c) => ({ ...c })),
annotations: memo.annotations.map((a) => ({ ...a })),
sectionReviews: memo.sectionReviews.map((r) => ({ ...r }))
};
}
function fail<T extends RpcMethod>(
code: "NOT_FOUND" | "VALIDATION_ERROR" | "INTERNAL_ERROR" | "AGENT_FAILED" | "CONFLICT" | "RATE_LIMITED",
message: string,
detail?: unknown
): RpcResult<T> {
return { ok: false, error: { code, message, detail } };
}

2456
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

7
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,7 @@
packages:
- "apps/*"
- "packages/*"
allowBuilds:
electron: true
esbuild: true

14
tsconfig.electron.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022", "DOM"],
"strict": true,
"skipLibCheck": true,
"outDir": "dist-electron",
"rootDir": ".",
"types": ["node", "electron"]
},
"include": ["apps/desktop/src/**/*", "packages/*/src/**/*"]
}

20
tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["apps/web/src", "packages/*/src", "vite.config.ts"]
}

17
vite.config.ts Normal file
View File

@@ -0,0 +1,17 @@
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
export default defineConfig({
root: "apps/web",
plugins: [react(), tailwindcss()],
server: {
host: "127.0.0.1",
port: 5173,
strictPort: true
},
build: {
outDir: "dist",
emptyOutDir: true
}
});

322
wireframe/app.js Normal file
View File

@@ -0,0 +1,322 @@
// ─── Agent data ───
const agentInfo = {
cr: { name: 'Company Research Agent', status: 'Completed · 100%', action: 'Company overview complete' },
fm: { name: 'Financial Modeling Agent', status: 'Running · 62% complete', action: 'Building revenue schedule…' },
sf: { name: 'SEC Filings Agent', status: 'Running · 45% complete', action: 'Extracting segment data from 10-K…' },
ec: { name: 'Earnings Call Agent', status: 'Running · 78% complete', action: 'Summarizing Q2 FY25 call…' },
ci: { name: 'Competitive Intel Agent', status: 'Queued · Waiting for SEC Filings', action: 'Queued' },
va: { name: 'Valuation Agent', status: 'Queued · Waiting for Financial Modeling', action: 'Queued' },
rk: { name: 'Risk Agent', status: 'Idle', action: 'Idle' },
mw: { name: 'Memo Writing Agent', status: 'Idle', action: 'Idle' },
pa: { name: 'Presentation Agent', status: 'Idle', action: 'Idle' },
mn: { name: 'Monitoring Agent', status: 'Idle', action: 'Idle' },
sv: { name: 'Source Verification Agent', status: 'Idle', action: 'Idle' },
rt: { name: 'Red Team Agent', status: 'Idle', action: 'Idle' },
ex: { name: 'Export Agent', status: 'Idle', action: 'Idle' },
qa: { name: 'Model QA Agent', status: 'Idle', action: 'Idle' },
};
const activeAgents = ['sf', 'fm', 'ec'];
let carouselIdx = 0;
// ─── Screen switching ───
function switchScreen(id, btn) {
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
document.getElementById('screen-' + id).classList.add('active');
document.querySelectorAll('.screen-tabs button').forEach(b => b.classList.remove('active'));
if (btn && btn.classList) btn.classList.add('active');
// Show/hide ticker group on home vs other screens
const tickerGroup = document.getElementById('tickerGroup');
const navBtn = document.getElementById('navOpenBtn');
if (id === 'home') {
if (tickerGroup) tickerGroup.style.display = 'none';
} else {
if (tickerGroup) tickerGroup.style.display = 'flex';
}
}
// ─── Collapsible nav (close completely) ───
function toggleNav(navId) {
const nav = document.getElementById(navId);
nav.classList.toggle('collapsed');
const btn = nav.querySelector('.collapse-btn');
if (nav.classList.contains('collapsed')) {
btn.textContent = '▶';
} else {
btn.textContent = '◀';
}
}
function openNav() {
const wsNav = document.getElementById('ws-nav');
const memoNav = document.getElementById('memo-nav');
[wsNav, memoNav].forEach(nav => {
if (nav && nav.classList.contains('collapsed')) {
nav.classList.remove('collapsed');
nav.querySelector('.collapse-btn').textContent = '◀';
}
});
}
// ─── Left nav button selection ───
document.querySelectorAll('#ws-nav button').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('#ws-nav button').forEach(b => b.classList.remove('active'));
this.classList.add('active');
});
});
// ─── Memo outline selection ───
document.querySelectorAll('.memo-outline li').forEach(li => {
li.addEventListener('click', function() {
document.querySelectorAll('.memo-outline li').forEach(l => l.classList.remove('active'));
this.classList.add('active');
});
});
// ─── Agent selection + detail panel ───
function selectAgent(tile, id) {
document.querySelectorAll('.agent-tile').forEach(t => t.classList.remove('selected'));
tile.classList.add('selected');
const detail = document.getElementById('agentDetail');
detail.classList.add('open');
document.getElementById('detailAgentName').textContent = agentInfo[id].name;
document.getElementById('detailAgentStatus').textContent = agentInfo[id].status;
}
function closeAgentDetail() {
document.getElementById('agentDetail').classList.remove('open');
document.querySelectorAll('.agent-tile').forEach(t => t.classList.remove('selected'));
}
// ─── Agent carousel ───
function updateCarousel() {
const agent = agentInfo[activeAgents[carouselIdx]];
document.getElementById('carouselName').textContent = agent.name;
document.getElementById('carouselAction').textContent = agent.action;
document.getElementById('carouselCounter').textContent = (carouselIdx + 1) + ' / ' + activeAgents.length;
document.getElementById('carouselPrev').disabled = carouselIdx === 0;
document.getElementById('carouselNext').disabled = carouselIdx === activeAgents.length - 1;
}
function carouselPrev() {
if (carouselIdx > 0) { carouselIdx--; updateCarousel(); }
}
function carouselNext() {
if (carouselIdx < activeAgents.length - 1) { carouselIdx++; updateCarousel(); }
}
updateCarousel();
// ─── Agent fullscreen overlay ───
function openAgentOverlay(agentId) {
const id = agentId || activeAgents[carouselIdx];
const agent = agentInfo[id];
document.getElementById('overlayAgentName').textContent = agent.name;
document.getElementById('overlayAgentStatus').textContent = agent.status;
// Activate the matching tab
document.querySelectorAll('.overlay-agent-tabs .oat-btn').forEach(btn => {
btn.classList.remove('active');
if (btn.textContent.trim().includes(agent.name.split(' ')[0])) btn.classList.add('active');
});
document.getElementById('agentOverlay').classList.add('open');
}
function closeAgentOverlay() {
document.getElementById('agentOverlay').classList.remove('open');
}
// ─── Settings overlay ───
function openSettings() { document.getElementById('settingsOverlay').classList.add('open'); }
function closeSettings() { document.getElementById('settingsOverlay').classList.remove('open'); }
// ─── Settings nav ───
document.querySelectorAll('.settings-nav button').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.settings-nav button').forEach(b => b.classList.remove('active'));
this.classList.add('active');
});
});
// ─── Review mode + annotations ───
let reviewMode = false;
let annotationMode = null;
function toggleReviewMode() {
reviewMode = !reviewMode;
const btn = document.getElementById('reviewToggle');
btn.textContent = reviewMode ? 'Exit Review' : 'Review Mode';
btn.style.background = reviewMode ? 'var(--accent)' : '';
btn.style.color = reviewMode ? 'var(--surface)' : '';
btn.style.borderColor = reviewMode ? 'var(--accent)' : '';
if (!reviewMode) {
annotationMode = null;
document.querySelectorAll('#modeHighlightBtn,#modeCommentBtn,#modeBoxBtn').forEach(b => {
b.style.background = ''; b.style.color = ''; b.style.borderColor = '';
});
}
}
function setAnnotationMode(mode) {
annotationMode = mode;
const btns = { highlight: 'modeHighlightBtn', comment: 'modeCommentBtn', box: 'modeBoxBtn' };
Object.entries(btns).forEach(([m, bid]) => {
const b = document.getElementById(bid);
b.style.background = m === mode ? 'var(--accent)' : '';
b.style.color = m === mode ? 'var(--surface)' : '';
b.style.borderColor = m === mode ? 'var(--accent)' : '';
});
}
document.addEventListener('mouseup', function(e) {
const toolbar = document.getElementById('annotationToolbar');
if (!reviewMode || !e.target.closest('#screen-memo .center')) {
toolbar.classList.remove('open'); return;
}
const sel = window.getSelection();
if (sel && sel.toString().trim().length > 0) {
const range = sel.getRangeAt(0);
const rect = range.getBoundingClientRect();
const center = e.target.closest('.center');
const cRect = center.getBoundingClientRect();
toolbar.style.top = (rect.top - cRect.top - 36 + center.scrollTop) + 'px';
toolbar.style.left = (rect.left - cRect.left + center.scrollLeft) + 'px';
toolbar.classList.add('open');
} else {
toolbar.classList.remove('open');
}
});
function annotateHighlight() {
const sel = window.getSelection();
if (!sel || sel.toString().trim().length === 0) return;
const range = sel.getRangeAt(0);
const span = document.createElement('span');
span.className = 'highlight-annotation';
span.setAttribute('data-comment', 'Highlighted by reviewer');
try { range.surroundContents(span); } catch(e) { span.textContent = sel.toString(); range.deleteContents(); range.insertNode(span); }
sel.removeAllRanges();
document.getElementById('annotationToolbar').classList.remove('open');
}
function annotateComment() {
const comment = prompt('Add a review comment:');
if (!comment) return;
const sel = window.getSelection();
if (!sel || sel.toString().trim().length === 0) return;
const range = sel.getRangeAt(0);
const span = document.createElement('span');
span.className = 'highlight-annotation';
span.setAttribute('data-comment', comment);
try { range.surroundContents(span); } catch(e) { span.textContent = sel.toString(); range.deleteContents(); range.insertNode(span); }
sel.removeAllRanges();
document.getElementById('annotationToolbar').classList.remove('open');
}
function annotateStrikethrough() {
const sel = window.getSelection();
if (!sel || sel.toString().trim().length === 0) return;
const range = sel.getRangeAt(0);
const span = document.createElement('span');
span.style.textDecoration = 'line-through';
span.style.color = 'var(--red)';
span.style.opacity = '0.6';
try { range.surroundContents(span); } catch(e) { span.textContent = sel.toString(); range.deleteContents(); range.insertNode(span); }
sel.removeAllRanges();
document.getElementById('annotationToolbar').classList.remove('open');
}
function annotateBox() {
document.getElementById('annotationToolbar').classList.remove('open');
const center = document.querySelector('#screen-memo .center');
const overlay = document.createElement('div');
overlay.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;z-index:150;cursor:crosshair;';
center.style.position = 'relative';
center.appendChild(overlay);
let startX, startY;
overlay.addEventListener('mousedown', function(e) {
const rect = overlay.getBoundingClientRect();
startX = e.clientX - rect.left + center.scrollLeft;
startY = e.clientY - rect.top + center.scrollTop;
const box = document.createElement('div');
box.className = 'box-annotation';
box.style.left = startX + 'px'; box.style.top = startY + 'px';
box.style.width = '0px'; box.style.height = '0px';
center.appendChild(box);
function onMove(ev) {
const cx = ev.clientX - rect.left + center.scrollLeft;
const cy = ev.clientY - rect.top + center.scrollTop;
box.style.width = Math.abs(cx - startX) + 'px';
box.style.height = Math.abs(cy - startY) + 'px';
box.style.left = Math.min(cx, startX) + 'px';
box.style.top = Math.min(cy, startY) + 'px';
}
function onUp() {
overlay.remove();
const label = document.createElement('div');
label.className = 'box-label';
label.textContent = 'Review needed';
label.onclick = function() { const note = prompt('Add note:', label.textContent); if (note) label.textContent = note; };
box.appendChild(label);
document.removeEventListener('mousemove', onMove);
document.removeEventListener('mouseup', onUp);
}
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
});
}
// ─── Model tab switching ───
function switchModelTab(tab) {
const spreadsheet = document.querySelector('#screen-model .spreadsheet');
const spreadsheetInfo = spreadsheet ? spreadsheet.parentElement.querySelector('div:last-of-type') : null;
const chartsPanel = document.getElementById('chartsPanel');
const allTables = document.querySelectorAll('#screen-model .center > *');
if (tab === 'charts') {
// Hide spreadsheet area, show charts
const center = document.querySelector('#screen-model .center');
if (center) {
// Hide everything except charts panel
Array.from(center.children).forEach(child => {
if (child.id !== 'chartsPanel') child.style.display = 'none';
});
}
if (chartsPanel) chartsPanel.style.display = 'grid';
} else {
// Show spreadsheet area, hide charts
const center = document.querySelector('#screen-model .center');
if (center) {
Array.from(center.children).forEach(child => {
if (child.id !== 'chartsPanel') child.style.display = '';
});
}
if (chartsPanel) chartsPanel.style.display = 'none';
}
}
// ─── Overlay tab switching (new tab-based layout) ───
function switchOverlayTab(btn, id) {
document.querySelectorAll('.overlay-agent-tabs .oat-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const agent = agentInfo[id];
document.getElementById('overlayAgentName').textContent = agent.name;
document.getElementById('overlayAgentStatus').textContent = agent.status;
}
// ─── Dep tree tab switching ───
function switchDepTab(btn, ticker) {
btn.parentElement.querySelectorAll('button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Future: swap dep tree content per ticker
}
// ─── Profile editor ───
function openProfile() { document.getElementById('profileOverlay').classList.add('open'); }
function closeProfile() { document.getElementById('profileOverlay').classList.remove('open'); }
function saveProfile() {
const name = document.getElementById('profileName').value;
const initials = name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2);
document.querySelector('.topbar .avatar').textContent = initials;
document.getElementById('profileDisplayName').textContent = name;
document.querySelector('.profile-overlay .profile-avatar').textContent = initials;
closeProfile();
}
// ─── Init: hide ticker on home screen ───
document.getElementById('tickerGroup').style.display = 'none';

1286
wireframe/index.html Normal file

File diff suppressed because it is too large Load Diff

1113
wireframe/styles.css Normal file

File diff suppressed because it is too large Load Diff