ompchamber 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/client/index.html +1 -1
- package/dist/client/static/css/index.5291f58223.css +2 -0
- package/dist/client/static/js/async/{1k.544adbf77d.js → 1k.5974df3b87.js} +2 -2
- package/dist/client/static/js/async/3j.357c80ff38.js +2 -0
- package/dist/client/static/js/async/{3y.22075aa079.js → 3y.ededeebde8.js} +5 -5
- package/dist/client/static/js/async/51.95bcae98af.js +2 -0
- package/dist/client/static/js/async/6.f034c9b528.js +1 -0
- package/dist/client/static/js/async/a.e05da304c8.js +10 -0
- package/dist/client/static/js/async/ar.1e14da5588.js +1 -0
- package/dist/client/static/js/async/ct.b74928f7a3.js +1 -0
- package/dist/client/static/js/async/dc.6dc4b0a04b.js +2 -0
- package/dist/client/static/js/async/fw.6a51998a8e.js +6 -0
- package/dist/client/static/js/async/g.7a4bbd4798.js +2 -0
- package/dist/client/static/js/async/g.7a4bbd4798.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{k.e878dd350f.js → k.7466330d1a.js} +2 -2
- package/dist/client/static/js/async/katex-vendor.de6a87c308.js +1 -0
- package/dist/client/static/js/async/kc.bbdb35b771.js +1 -0
- package/dist/client/static/js/async/l6.c56640c205.js +1 -0
- package/dist/client/static/js/async/{m.3fc017a055.js → m.4b29d34f93.js} +3 -3
- package/dist/client/static/js/async/{og.ae7acf03ac.js → og.634fba99ac.js} +2 -2
- package/dist/client/static/js/async/q.7fb4d2f374.js +21 -0
- package/dist/client/static/js/async/rd.cb6bf495bb.js +2 -0
- package/dist/client/static/js/async/rf.d626afc367.js +2 -0
- package/dist/client/static/js/async/s.413c2a4683.js +2 -0
- package/dist/client/static/js/async/s.413c2a4683.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/sy.d16be843a7.js +94 -0
- package/dist/client/static/js/async/t.cb3fcf8615.js +1 -0
- package/dist/client/static/js/async/ud.3065bfee31.js +2 -0
- package/dist/client/static/js/async/uf.46ad6209fa.js +2 -0
- package/dist/client/static/js/async/uf.46ad6209fa.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/v.7d28d3c241.js +34 -0
- package/dist/client/static/js/async/v0.394ed37c6b.js +2 -0
- package/dist/client/static/js/async/v0.394ed37c6b.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/vt.42416b707d.js +1 -0
- package/dist/client/static/js/async/vy.bbe114c4e0.js +2 -0
- package/dist/client/static/js/async/vy.bbe114c4e0.js.LICENSE.txt +6 -0
- package/dist/client/static/js/async/xw.ae67ba06fd.js +31 -0
- package/dist/client/static/js/async/xw.ae67ba06fd.js.LICENSE.txt +4 -0
- package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
- package/dist/client/static/js/async/yv.2fa22f009a.js +1 -0
- package/dist/client/static/js/index.0ac93ee878.js +33 -0
- package/dist/client/static/js/index.0ac93ee878.js.LICENSE.txt +4 -0
- package/dist/client/static/js/{markdown-vendor.9da142c67b.js → markdown-vendor.71e43278b3.js} +1 -1
- package/dist/client/static/js/uy.5f791144ce.js +139 -0
- package/package.json +2 -2
- package/src/cli/lib/args.js +1 -0
- package/src/cli/lib/commands/serve.js +7 -0
- package/src/client/App.tsx +22 -5
- package/src/client/components/common/MarkdownRenderer.tsx +36 -0
- package/src/client/components/layout/desktop-layout/index.tsx +1 -1
- package/src/client/components/layout/session-sidebar/Modals.tsx +1 -1
- package/src/client/components/mobile/LayoutWrapper.tsx +1 -1
- package/src/client/components/settings/LazyModal.tsx +46 -0
- package/src/client/components/settings/Modal.tsx +1 -1
- package/src/client/components/workspace/chat-timeline/EmptyWorkspacePrompt.tsx +43 -29
- package/src/client/components/workspace/chat-timeline/MessageItem.tsx +15 -69
- package/src/client/components/workspace/chat-timeline/MessageList.tsx +47 -66
- package/src/client/components/workspace/chat-timeline/RunFooter.tsx +60 -0
- package/src/client/components/workspace/chat-timeline/SubagentView.tsx +37 -21
- package/src/client/components/workspace/chat-timeline/ToolCallCard.tsx +22 -4
- package/src/client/components/workspace/chat-timeline/index.tsx +3 -6
- package/src/client/components/workspace/chat-timeline/tool-renderers/panels/Edit.tsx +15 -19
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/EnvelopeHeader.tsx +46 -0
- package/src/client/components/workspace/chat-timeline/tool-renderers/shared/FallbackOutput.tsx +18 -68
- package/src/client/components/workspace/file-explorer/TreeItem.tsx +16 -4
- package/src/client/components/workspace/file-explorer/index.tsx +6 -0
- package/src/client/data/models/catalog.ts +8 -0
- package/src/client/data/samples/dialogue-session.ts +1 -1
- package/src/client/data/samples/sample-tools/other.ts +1 -1
- package/src/client/hooks/chat/omp/index.ts +9 -10
- package/src/client/hooks/chat/omp/revalidation-throttle.ts +6 -5
- package/src/client/hooks/chat/timeline/actions.ts +1 -2
- package/src/client/hooks/chat/timeline/index.ts +5 -0
- package/src/client/hooks/chat/timeline/send.ts +2 -3
- package/src/client/hooks/chat/timeline/session-load.ts +3 -4
- package/src/client/hooks/ui/syntax-ready.ts +16 -3
- package/src/client/main.tsx +2 -5
- package/src/client/stubs/elkjs.ts +37 -0
- package/src/client/tailwind.css +10 -0
- package/src/server/index.ts +53 -0
- package/src/server/lib/fs/git-ignore.ts +49 -0
- package/src/server/lib/models/provider-registry.server.ts +44 -20
- package/src/server/lib/omp/core/cli.ts +3 -2
- package/src/server/lib/omp/core/startup.ts +64 -0
- package/src/server/lib/omp/rpc/constants.ts +10 -0
- package/src/server/lib/omp/rpc/lifecycle.test.ts +138 -0
- package/src/server/lib/omp/rpc/manager.ts +32 -4
- package/src/server/lib/omp/rpc/process.ts +1 -1
- package/src/server/lib/omp/rpc/session-commands.ts +32 -9
- package/src/server/lib/omp/rpc/session-registry.ts +3 -2
- package/src/server/lib/omp/rpc/subagent-liveness.test.ts +62 -0
- package/src/server/lib/omp/rpc/subagent-liveness.ts +100 -0
- package/src/server/lib/omp/session/messages.test.ts +112 -0
- package/src/server/lib/omp/session/messages.ts +6 -5
- package/src/server/mock.server.ts +6 -12
- package/src/server/routes/agent/command.ts +34 -16
- package/src/server/routes/chat/stream.ts +13 -30
- package/src/server/routes/fs/read.ts +15 -7
- package/src/server/routes/models/root.ts +5 -1
- package/src/shared/lib/chat/notice-row.test.ts +89 -0
- package/src/shared/lib/chat/notice-row.ts +78 -0
- package/src/shared/lib/chat/omp/abort.ts +41 -0
- package/src/shared/lib/chat/stream-service.ts +0 -76
- package/src/shared/lib/chat/timeline/omp-callbacks.test.ts +154 -0
- package/src/shared/lib/chat/timeline/omp-callbacks.ts +36 -7
- package/src/shared/lib/chat/timeline/run-footer.test.ts +84 -0
- package/src/shared/lib/chat/timeline/run-footer.ts +96 -0
- package/src/shared/lib/chat/tool-output.test.ts +91 -0
- package/src/shared/lib/chat/tool-output.ts +54 -0
- package/src/shared/lib/chat/xml-envelope.ts +158 -0
- package/src/shared/lib/code/highlighter-lazy.ts +106 -0
- package/src/shared/lib/code/highlighter.ts +31 -52
- package/src/shared/lib/code/syntax-highlight.test.ts +36 -1
- package/src/shared/lib/code/syntax-highlight.ts +17 -3
- package/src/shared/lib/format/time.ts +19 -0
- package/src/shared/lib/fs/paths.ts +19 -0
- package/src/shared/lib/markdown/fira-code-fonts.css +51 -0
- package/src/shared/lib/markdown/katex-fonts.css +20 -0
- package/src/shared/lib/markdown/katex.ts +101 -0
- package/src/shared/lib/markdown/marked.ts +23 -14
- package/src/shared/lib/markdown/math.test.ts +109 -0
- package/src/shared/lib/markdown/mermaid.ts +6 -0
- package/src/shared/lib/markdown/sanitize.ts +48 -2
- package/src/shared/lib/omp/session/mapper.ts +11 -3
- package/src/shared/lib/omp/session/messages-map.ts +12 -12
- package/src/shared/types/chat.ts +3 -1
- package/src/shared/types/fs.ts +3 -0
- package/dist/client/static/css/g.f36aa429fa.css +0 -1
- package/dist/client/static/css/index.a0f07455bd.css +0 -2
- package/dist/client/static/css/katex-vendor.ca3a0600e8.css +0 -1
- package/dist/client/static/font/KaTeX_AMS-Regular.1608a09b4a.woff +0 -0
- package/dist/client/static/font/KaTeX_AMS-Regular.4aafdb685c.ttf +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.b67709187b.woff +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Bold.cce5b8ecea.ttf +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.07ef19e7b0.ttf +0 -0
- package/dist/client/static/font/KaTeX_Caligraphic-Regular.dad44a7fc6.woff +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.9f256b8593.woff +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Bold.b18f59e1d1.ttf +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.7c1871215e.woff +0 -0
- package/dist/client/static/font/KaTeX_Fraktur-Regular.ed38e79f57.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.b74a1a8b2c.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-Bold.d181c4650d.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.70d8b0a530.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-BoldItalic.e3f82f9d27.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.47373d1e51.ttf +0 -0
- package/dist/client/static/font/KaTeX_Main-Italic.9024d815ba.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.7f51fe0340.woff +0 -0
- package/dist/client/static/font/KaTeX_Main-Regular.b7f8fe9b5f.ttf +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.a879cf8383.ttf +0 -0
- package/dist/client/static/font/KaTeX_Math-BoldItalic.f1035d8d5d.woff +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.5295ba483a.woff +0 -0
- package/dist/client/static/font/KaTeX_Math-Italic.939bc64440.ttf +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.94e1e8dc5f.ttf +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Bold.bf59d231c3.woff +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.7c9bc82b17.woff +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Italic.b4c20c84d8.ttf +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.740484788f.woff +0 -0
- package/dist/client/static/font/KaTeX_SansSerif-Regular.d4d7ba4804.ttf +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.0750571032.woff +0 -0
- package/dist/client/static/font/KaTeX_Script-Regular.fe9cbbe1a0.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.e1e279cbdd.woff +0 -0
- package/dist/client/static/font/KaTeX_Size1-Regular.fabc004aab.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.577270225e.woff +0 -0
- package/dist/client/static/font/KaTeX_Size2-Regular.d6b476ecd3.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size3-Regular.9acaf01c64.woff +0 -0
- package/dist/client/static/font/KaTeX_Size3-Regular.a144ef5840.ttf +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.7a996c9da2.woff +0 -0
- package/dist/client/static/font/KaTeX_Size4-Regular.fbccdabe0a.ttf +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.6258592bdc.woff +0 -0
- package/dist/client/static/font/KaTeX_Typewriter-Regular.d97aaf4a1e.ttf +0 -0
- package/dist/client/static/font/fira-code-cyrillic-400-normal.5971438a4a.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-400-normal.9bb7bfea9c.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-500-normal.5486265b18.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-500-normal.89f92bfb31.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-600-normal.2805b497e5.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-600-normal.86e0df22a3.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-700-normal.505a88f22a.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-700-normal.76f5e8d2e2.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-400-normal.3c4b15e57d.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-400-normal.61323debaa.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-500-normal.0f34210f2a.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-500-normal.2497cfa1aa.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-600-normal.40fce94af9.woff2 +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-600-normal.6c8774b829.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-700-normal.4b742eb865.woff +0 -0
- package/dist/client/static/font/fira-code-cyrillic-ext-700-normal.e75eb4fccc.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-400-normal.bb900468b6.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-400-normal.be76e699ba.woff +0 -0
- package/dist/client/static/font/fira-code-greek-500-normal.176c9fb001.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-500-normal.d2c596136b.woff +0 -0
- package/dist/client/static/font/fira-code-greek-600-normal.490c3deed7.woff +0 -0
- package/dist/client/static/font/fira-code-greek-600-normal.7df14376d9.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-700-normal.c82b6072fc.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-700-normal.d5f2b2917b.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-400-normal.4849a79781.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-400-normal.7f5bfc4538.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-ext-500-normal.aaccca4075.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-ext-500-normal.e383181256.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-600-normal.78cb4bff8e.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-600-normal.d6c93d54f9.woff2 +0 -0
- package/dist/client/static/font/fira-code-greek-ext-700-normal.57ec7c92d9.woff +0 -0
- package/dist/client/static/font/fira-code-greek-ext-700-normal.8fafdae5e8.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-400-normal.973295532b.woff +0 -0
- package/dist/client/static/font/fira-code-latin-500-normal.7ae2be8fb8.woff +0 -0
- package/dist/client/static/font/fira-code-latin-600-normal.1dbbe5bc27.woff +0 -0
- package/dist/client/static/font/fira-code-latin-700-normal.497a70148b.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-400-normal.9dfae29aaf.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-400-normal.e534ae46e2.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-500-normal.24c7b781b0.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-500-normal.aad44ab072.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-600-normal.7bed0f2f7e.woff +0 -0
- package/dist/client/static/font/fira-code-latin-ext-600-normal.c509a7c9f1.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-700-normal.6c66c8b0a9.woff2 +0 -0
- package/dist/client/static/font/fira-code-latin-ext-700-normal.88f64e2822.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-400-normal.97d3f4b17c.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-500-normal.b06f1a06e4.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-600-normal.304b653b93.woff +0 -0
- package/dist/client/static/font/fira-code-symbols2-700-normal.d8ce0ad586.woff +0 -0
- package/dist/client/static/js/async/3j.a2fcea99be.js +0 -2
- package/dist/client/static/js/async/51.1938cec320.js +0 -2
- package/dist/client/static/js/async/6.7929428505.js +0 -1
- package/dist/client/static/js/async/69.a18a10882f.js +0 -1
- package/dist/client/static/js/async/a.bce4542392.js +0 -1
- package/dist/client/static/js/async/ar.9dc8b1992a.js +0 -2
- package/dist/client/static/js/async/bi.cbfe8eaa8e.js +0 -1
- package/dist/client/static/js/async/ct.38d9da8a5a.js +0 -1
- package/dist/client/static/js/async/fw.fb1a08b92d.js +0 -6
- package/dist/client/static/js/async/l6.45f3b62e15.js +0 -1
- package/dist/client/static/js/async/q.0b6fb9bf8d.js +0 -21
- package/dist/client/static/js/async/rd.b77011ab7f.js +0 -2
- package/dist/client/static/js/async/rf.cfbde7f744.js +0 -2
- package/dist/client/static/js/async/s.c7d521a8ae.js +0 -2
- package/dist/client/static/js/async/sy.7345abcd67.js +0 -94
- package/dist/client/static/js/async/t.0dcb01dfde.js +0 -1
- package/dist/client/static/js/async/v.5c7a4b087b.js +0 -1
- package/dist/client/static/js/async/vu.311f1d202c.js +0 -34
- package/dist/client/static/js/async/yg.9bd4e09ecb.js +0 -149
- package/dist/client/static/js/async/yv.593a3f7d54.js +0 -1
- package/dist/client/static/js/g.124fba0826.js +0 -139
- package/dist/client/static/js/index.907fd4670e.js +0 -68
- package/dist/client/static/js/katex-vendor.95e48cf65a.js +0 -1
- package/src/shared/lib/chat/order.ts +0 -40
- /package/dist/client/static/js/async/{1k.544adbf77d.js.LICENSE.txt → 1k.5974df3b87.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3j.a2fcea99be.js.LICENSE.txt → 3j.357c80ff38.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.22075aa079.js.LICENSE.txt → 3y.ededeebde8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{51.1938cec320.js.LICENSE.txt → 51.95bcae98af.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ar.9dc8b1992a.js.LICENSE.txt → a.e05da304c8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{fw.fb1a08b92d.js.LICENSE.txt → dc.6dc4b0a04b.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{m.3fc017a055.js.LICENSE.txt → fw.6a51998a8e.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.0b6fb9bf8d.js.LICENSE.txt → m.4b29d34f93.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{rf.cfbde7f744.js.LICENSE.txt → q.7fb4d2f374.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{rd.b77011ab7f.js.LICENSE.txt → rd.cb6bf495bb.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{s.c7d521a8ae.js.LICENSE.txt → rf.d626afc367.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.907fd4670e.js.LICENSE.txt → async/ud.3065bfee31.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{g.124fba0826.js.LICENSE.txt → uy.5f791144ce.js.LICENSE.txt} +0 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Tool output sometimes arrives wrapped in a single XML envelope: omp's
|
|
8
|
+
* `<system-reminder …>…</system-reminder>`, an MCP server's `<result>…</result>`.
|
|
9
|
+
* The tag is transport, not content — rendered as markdown it would show as
|
|
10
|
+
* escaped markup instead of the message it carries, so the timeline peels the
|
|
11
|
+
* wrapper before rendering the inner text.
|
|
12
|
+
*
|
|
13
|
+
* Only a wrapper that OPENS the output is peeled (a tag in the middle of prose
|
|
14
|
+
* is content: `<div>` inside an HTML sample, `a < b`), its tags must balance
|
|
15
|
+
* and nest, and what follows the closing tag must be free text — a second
|
|
16
|
+
* element means there is no single wrapper, so nothing is peeled.
|
|
17
|
+
*
|
|
18
|
+
* The scan is hand-rolled: this module is shared with the server (no
|
|
19
|
+
* `DOMParser` there) and parses arbitrary tool output, so it must never throw.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface XmlEnvelope {
|
|
23
|
+
/** Element name, e.g. `system-reminder`. */
|
|
24
|
+
tag: string;
|
|
25
|
+
/** Attributes of the wrapper's start tag, e.g. `{ reason: 'rule_violation' }`. */
|
|
26
|
+
attributes: Record<string, string>;
|
|
27
|
+
/** Text between the wrapper's start and end tags, common indent removed. */
|
|
28
|
+
inner: string;
|
|
29
|
+
/** Free text that followed the closing tag, if any. */
|
|
30
|
+
rest?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const NAME_RE = /^[A-Za-z_][A-Za-z0-9_.:-]*/;
|
|
34
|
+
const ATTR_RE = /([A-Za-z_:][A-Za-z0-9_.:-]*)\s*(?:=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
|
|
35
|
+
|
|
36
|
+
interface Tag {
|
|
37
|
+
kind: 'start' | 'end' | 'self' | 'other';
|
|
38
|
+
name: string;
|
|
39
|
+
/** Raw text between the name and the closing `>`. */
|
|
40
|
+
attrs: string;
|
|
41
|
+
/** Index just past the tag's `>`. */
|
|
42
|
+
end: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Attributes of a start tag: `reason="rule_violation" flag`. */
|
|
46
|
+
function parseAttributes(raw: string): Record<string, string> {
|
|
47
|
+
const attributes: Record<string, string> = {};
|
|
48
|
+
for (const match of raw.matchAll(ATTR_RE)) {
|
|
49
|
+
attributes[match[1]] = match[2] ?? match[3] ?? match[4] ?? '';
|
|
50
|
+
}
|
|
51
|
+
return attributes;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Envelope tags that mean "the runtime interrupted with a reminder" — the
|
|
55
|
+
* card header flags them instead of treating them as an ordinary result. */
|
|
56
|
+
export function isReminderTag(tag: string): boolean {
|
|
57
|
+
return tag === 'system-reminder' || tag === 'reminder';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Index of the tag's closing `>` — an attribute value may contain `>`. */
|
|
61
|
+
function tagEnd(text: string, from: number): number {
|
|
62
|
+
let quote = '';
|
|
63
|
+
for (let i = from; i < text.length; i += 1) {
|
|
64
|
+
const ch = text[i];
|
|
65
|
+
if (quote) {
|
|
66
|
+
if (ch === quote) quote = '';
|
|
67
|
+
} else if (ch === '"' || ch === "'") {
|
|
68
|
+
quote = ch;
|
|
69
|
+
} else if (ch === '>') {
|
|
70
|
+
return i;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return -1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Read the markup starting at `<`. Comments, CDATA and processing
|
|
77
|
+
* instructions come back as `other` so their contents can never be read as
|
|
78
|
+
* tags. Returns undefined for anything malformed. */
|
|
79
|
+
function readTag(text: string, at: number): Tag | undefined {
|
|
80
|
+
const head = text.slice(at, at + 9);
|
|
81
|
+
if (head.startsWith('<!--')) {
|
|
82
|
+
const close = text.indexOf('-->', at + 4);
|
|
83
|
+
return close === -1 ? undefined : { kind: 'other', name: '', attrs: '', end: close + 3 };
|
|
84
|
+
}
|
|
85
|
+
if (head === '<![CDATA[') {
|
|
86
|
+
const close = text.indexOf(']]>', at + 9);
|
|
87
|
+
return close === -1 ? undefined : { kind: 'other', name: '', attrs: '', end: close + 3 };
|
|
88
|
+
}
|
|
89
|
+
if (text[at + 1] === '!' || text[at + 1] === '?') {
|
|
90
|
+
const close = tagEnd(text, at + 2);
|
|
91
|
+
return close === -1 ? undefined : { kind: 'other', name: '', attrs: '', end: close + 1 };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const closing = text[at + 1] === '/';
|
|
95
|
+
const nameAt = at + (closing ? 2 : 1);
|
|
96
|
+
const name = NAME_RE.exec(text.slice(nameAt, nameAt + 64))?.[0];
|
|
97
|
+
if (!name) return undefined;
|
|
98
|
+
const close = tagEnd(text, nameAt + name.length);
|
|
99
|
+
if (close === -1) return undefined;
|
|
100
|
+
const attrs = text.slice(nameAt + name.length, close);
|
|
101
|
+
if (closing) return { kind: 'end', name, attrs, end: close + 1 };
|
|
102
|
+
return { kind: text[close - 1] === '/' ? 'self' : 'start', name, attrs, end: close + 1 };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Drop the indentation every line shares — XML pretty-printing padding that
|
|
106
|
+
* markdown would otherwise read as an indented code block. */
|
|
107
|
+
function stripCommonIndent(text: string): string {
|
|
108
|
+
const lines = text.split('\n');
|
|
109
|
+
// The line breaks hugging the wrapper tags are padding, not content.
|
|
110
|
+
if (lines.length > 1 && !lines[0].trim()) lines.shift();
|
|
111
|
+
while (lines.length > 1 && !lines[lines.length - 1].trim()) lines.pop();
|
|
112
|
+
|
|
113
|
+
let indent = Number.POSITIVE_INFINITY;
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
if (line.trim()) indent = Math.min(indent, line.length - line.trimStart().length);
|
|
116
|
+
}
|
|
117
|
+
if (!Number.isFinite(indent)) return '';
|
|
118
|
+
if (indent === 0) return lines.join('\n');
|
|
119
|
+
return lines.map((line) => line.slice(Math.min(indent, line.length - line.trimStart().length))).join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Peel the XML wrapper when it opens the output and encloses a whole
|
|
123
|
+
* element. Returns undefined when the output is not such an envelope. */
|
|
124
|
+
export function unwrapXmlEnvelope(text: string): XmlEnvelope | undefined {
|
|
125
|
+
const opening = text.search(/\S/);
|
|
126
|
+
if (opening === -1 || text[opening] !== '<') return undefined;
|
|
127
|
+
|
|
128
|
+
const root = readTag(text, opening);
|
|
129
|
+
if (!root || root.kind !== 'start') return undefined;
|
|
130
|
+
|
|
131
|
+
const stack = [root.name];
|
|
132
|
+
for (let i = root.end; i < text.length; ) {
|
|
133
|
+
if (text[i] !== '<') {
|
|
134
|
+
i += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const tag = readTag(text, i);
|
|
138
|
+
if (!tag) return undefined;
|
|
139
|
+
if (tag.kind === 'start') {
|
|
140
|
+
stack.push(tag.name);
|
|
141
|
+
} else if (tag.kind === 'end') {
|
|
142
|
+
if (stack.pop() !== tag.name) return undefined;
|
|
143
|
+
if (stack.length === 0) {
|
|
144
|
+
const rest = text.slice(tag.end).trim();
|
|
145
|
+
// A sibling element means there is no single wrapper to peel.
|
|
146
|
+
if (rest.startsWith('<')) return undefined;
|
|
147
|
+
return {
|
|
148
|
+
tag: root.name,
|
|
149
|
+
attributes: parseAttributes(root.attrs),
|
|
150
|
+
inner: stripCommonIndent(text.slice(root.end, i)),
|
|
151
|
+
...(rest ? { rest } : {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
i = tag.end;
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Demand-driven grammar loading for the Shiki core highlighter.
|
|
8
|
+
*
|
|
9
|
+
* `shiki/core` exposes only a synchronous `codeToHtml`: a grammar that was never
|
|
10
|
+
* passed to `loadLanguage` throws, and there is no internal lazy fetch. So the
|
|
11
|
+
* grammar for a language is fetched the first time that language is actually
|
|
12
|
+
* asked for — not for all 58 at boot, which produced 58 parallel chunk requests
|
|
13
|
+
* and ~8 MB of JS for a session that touches three languages.
|
|
14
|
+
*
|
|
15
|
+
* A grammar load is fire-and-forget: callers report the miss synchronously
|
|
16
|
+
* (falling back to escaped plain text) and subscribe via `onLanguageReady` to be
|
|
17
|
+
* told when the grammar landed, so the same highlight can be retried.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { HighlighterCore, LanguageRegistration } from 'shiki/core';
|
|
21
|
+
|
|
22
|
+
import { LANG_LOADERS } from '@/shared/lib/code/shiki-langs';
|
|
23
|
+
|
|
24
|
+
/** Shape of a `@shikijs/langs/*` module (the registration array sits on `.default`). */
|
|
25
|
+
type LangModule = { default: LanguageRegistration[] };
|
|
26
|
+
|
|
27
|
+
type Listener = () => void;
|
|
28
|
+
|
|
29
|
+
/** Languages currently being fetched — dedupes concurrent requests per grammar. */
|
|
30
|
+
const pending = new Map<string, Promise<void>>();
|
|
31
|
+
/** Languages that loaded (or that Shiki treats as built-in, e.g. `text`). */
|
|
32
|
+
const loaded = new Set<string>();
|
|
33
|
+
const listeners = new Set<Listener>();
|
|
34
|
+
|
|
35
|
+
/** Shiki rejects special languages; `text` is always available and needs no fetch. */
|
|
36
|
+
const SPECIAL_LANGS: Record<string, true> = {
|
|
37
|
+
text: true,
|
|
38
|
+
plain: true,
|
|
39
|
+
plaintext: true,
|
|
40
|
+
txt: true,
|
|
41
|
+
ansi: true,
|
|
42
|
+
none: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function notify(): void {
|
|
46
|
+
for (const fn of listeners) fn();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* True once `language` can be highlighted synchronously. Special languages are
|
|
51
|
+
* always usable; every other id is usable only after its grammar resolved.
|
|
52
|
+
*/
|
|
53
|
+
export function isLanguageReady(language: string): boolean {
|
|
54
|
+
return SPECIAL_LANGS[language] === true || loaded.has(language);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Subscribe to grammar arrivals. Returns an unsubscribe fn. */
|
|
58
|
+
export function onLanguageReady(fn: Listener): () => void {
|
|
59
|
+
listeners.add(fn);
|
|
60
|
+
return () => {
|
|
61
|
+
listeners.delete(fn);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ensure `language` is loaded, resolving on completion. Callers stay
|
|
67
|
+
* synchronous: they render the plain-text fallback and retry on the next
|
|
68
|
+
* `onLanguageReady` fire, identical to the previous warmup-race behaviour.
|
|
69
|
+
*
|
|
70
|
+
* Unknown ids (not in `LANG_LOADERS`) resolve immediately — the caller's own
|
|
71
|
+
* fallback language handles them.
|
|
72
|
+
*/
|
|
73
|
+
export function ensureLanguage(highlighter: HighlighterCore, language: string): Promise<void> {
|
|
74
|
+
if (isLanguageReady(language)) return Promise.resolve();
|
|
75
|
+
const inFlight = pending.get(language);
|
|
76
|
+
if (inFlight) return inFlight;
|
|
77
|
+
const loader = LANG_LOADERS[language];
|
|
78
|
+
if (!loader) return Promise.resolve();
|
|
79
|
+
|
|
80
|
+
const load = highlighter
|
|
81
|
+
.loadLanguage(loader as () => Promise<LangModule>)
|
|
82
|
+
.then(() => {
|
|
83
|
+
loaded.add(language);
|
|
84
|
+
notify();
|
|
85
|
+
})
|
|
86
|
+
.catch(() => {
|
|
87
|
+
// Grammar unavailable under this engine: the caller keeps the plain
|
|
88
|
+
// fallback forever, and the id is left out of `loaded` so a later retry
|
|
89
|
+
// is still possible.
|
|
90
|
+
})
|
|
91
|
+
.finally(() => {
|
|
92
|
+
pending.delete(language);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
pending.set(language, load);
|
|
96
|
+
return load;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Load the eager set without blocking boot. Resolves when every eager grammar
|
|
101
|
+
* settled, which is what the previous `WARMUP_BUDGET_MS` race approximated —
|
|
102
|
+
* now nothing else waits on it, so a slow grammar cannot stall readiness.
|
|
103
|
+
*/
|
|
104
|
+
export async function ensureLanguages(highlighter: HighlighterCore, languages: readonly string[]): Promise<void> {
|
|
105
|
+
await Promise.all(languages.map((id) => ensureLanguage(highlighter, id)));
|
|
106
|
+
}
|
|
@@ -4,23 +4,18 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { createHighlighterCore } from 'shiki/core';
|
|
7
|
-
import type { HighlighterCore
|
|
7
|
+
import type { HighlighterCore } from 'shiki/core';
|
|
8
8
|
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript';
|
|
9
9
|
import oneLight from '@shikijs/themes/one-light';
|
|
10
10
|
import oneDarkPro from '@shikijs/themes/one-dark-pro';
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
* The transpile cost of the first highlight for a grammar is ~700 ms, so warming
|
|
20
|
-
* all 58 would stall readiness. Cap the warmup: if the eager set is not done in
|
|
21
|
-
* this budget the rest load lazily on first use instead of blocking boot.
|
|
22
|
-
*/
|
|
23
|
-
const WARMUP_BUDGET_MS = 1500;
|
|
12
|
+
import {
|
|
13
|
+
ensureLanguage,
|
|
14
|
+
ensureLanguages,
|
|
15
|
+
isLanguageReady,
|
|
16
|
+
onLanguageReady as _onLanguageReady,
|
|
17
|
+
} from '@/shared/lib/code/highlighter-lazy';
|
|
18
|
+
import { EAGER_LANGS } from '@/shared/lib/code/shiki-langs';
|
|
24
19
|
|
|
25
20
|
let instance: HighlighterCore | null = null;
|
|
26
21
|
let ready = false;
|
|
@@ -31,10 +26,6 @@ export function escapeCode(code: string): string {
|
|
|
31
26
|
return code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
32
27
|
}
|
|
33
28
|
|
|
34
|
-
function delay(ms: number): Promise<void> {
|
|
35
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
29
|
function notify(): void {
|
|
39
30
|
const pending = [...listeners];
|
|
40
31
|
listeners.clear();
|
|
@@ -47,38 +38,6 @@ function notify(): void {
|
|
|
47
38
|
}
|
|
48
39
|
}
|
|
49
40
|
|
|
50
|
-
/**
|
|
51
|
-
* Register every grammar so `getLoadedLanguages()` covers the full registry
|
|
52
|
-
* from the first paint-after-ready. Registration is a parallel chunk fetch
|
|
53
|
-
* (fast); the expensive per-grammar transpile stays in `warmup` below.
|
|
54
|
-
*/
|
|
55
|
-
async function loadAllLanguages(highlighter: HighlighterCore): Promise<void> {
|
|
56
|
-
await Promise.allSettled(
|
|
57
|
-
Object.keys(LANG_LOADERS).map(async (id) => {
|
|
58
|
-
const loader = LANG_LOADERS[id];
|
|
59
|
-
if (!loader) return;
|
|
60
|
-
try {
|
|
61
|
-
await highlighter.loadLanguage(loader as () => Promise<LangModule>);
|
|
62
|
-
} catch {
|
|
63
|
-
// Grammar unavailable under the JS engine — per-call fallback covers it.
|
|
64
|
-
}
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/** Pre-transpile the eager grammars; each failure is independent and ignored. */
|
|
70
|
-
async function warmup(highlighter: HighlighterCore): Promise<void> {
|
|
71
|
-
await Promise.all(
|
|
72
|
-
EAGER_LANGS.map(async (id) => {
|
|
73
|
-
try {
|
|
74
|
-
highlighter.codeToHtml('x', { lang: id, theme: SHIKI_LIGHT });
|
|
75
|
-
} catch {
|
|
76
|
-
// Grammar unavailable under the JS engine — falls back to plain text.
|
|
77
|
-
}
|
|
78
|
-
}),
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
41
|
async function boot(): Promise<HighlighterCore | null> {
|
|
83
42
|
if (typeof window === 'undefined') return null;
|
|
84
43
|
try {
|
|
@@ -88,10 +47,12 @@ async function boot(): Promise<HighlighterCore | null> {
|
|
|
88
47
|
engine: createJavaScriptRegexEngine({ forgiving: true }),
|
|
89
48
|
});
|
|
90
49
|
instance = highlighter;
|
|
91
|
-
await loadAllLanguages(highlighter);
|
|
92
|
-
await Promise.race([warmup(highlighter), delay(WARMUP_BUDGET_MS)]);
|
|
93
50
|
ready = true;
|
|
94
51
|
notify();
|
|
52
|
+
// Grammars arrive on demand (`ensureLanguage`); the eager set is only a
|
|
53
|
+
// head start for the languages a chat session hits immediately, and boot
|
|
54
|
+
// does not wait on it.
|
|
55
|
+
void ensureLanguages(highlighter, EAGER_LANGS);
|
|
95
56
|
return highlighter;
|
|
96
57
|
} catch (error) {
|
|
97
58
|
console.error('[shiki] highlighter boot failed', error);
|
|
@@ -129,7 +90,25 @@ export function onSyntaxReady(fn: () => void): () => void {
|
|
|
129
90
|
};
|
|
130
91
|
}
|
|
131
92
|
|
|
132
|
-
/** The highlighter, or `null` before
|
|
93
|
+
/** The highlighter, or `null` before boot completes. */
|
|
133
94
|
export function getHighlighterSync(): HighlighterCore | null {
|
|
134
95
|
return ready ? instance : null;
|
|
135
96
|
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Request the grammar for `language`, if the highlighter is booted. Returns
|
|
100
|
+
* whether the grammar is usable synchronously *right now* — callers that get
|
|
101
|
+
* `false` must render the plain-text fallback and re-run once
|
|
102
|
+
* `onLanguageReady`/`onSyntaxReady` fires.
|
|
103
|
+
*
|
|
104
|
+
* This is the only path that pulls a grammar chunk off the network, so it is
|
|
105
|
+
* deliberately pull-based: a language nobody renders is never fetched.
|
|
106
|
+
*/
|
|
107
|
+
export function requestLanguage(language: string): boolean {
|
|
108
|
+
if (!instance) return false;
|
|
109
|
+
void ensureLanguage(instance, language);
|
|
110
|
+
return isLanguageReady(language);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Fires whenever a grammar lands, so pending highlights can retry. */
|
|
114
|
+
export const onLanguageReady = _onLanguageReady;
|
|
@@ -5,13 +5,37 @@
|
|
|
5
5
|
|
|
6
6
|
import { beforeAll, describe, expect, test } from 'bun:test';
|
|
7
7
|
|
|
8
|
-
import { bootSyntax } from '@/shared/lib/code/highlighter';
|
|
8
|
+
import { bootSyntax, onLanguageReady, requestLanguage } from '@/shared/lib/code/highlighter';
|
|
9
9
|
import { getLanguageFromPath, highlightCode } from '@/shared/lib/code/syntax-highlight';
|
|
10
10
|
|
|
11
11
|
/** Shiki emits `<span class="shiki">…` with dual-theme CSS variables per token. */
|
|
12
12
|
const SHIKI_RE = /<span class="shiki">/;
|
|
13
13
|
const SHIKI_TOKEN_RE = /--shiki-light:/;
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Every language these tests highlight. Grammars are fetched on demand, so the
|
|
17
|
+
* suite asks for them up front and waits for the last one to land instead of
|
|
18
|
+
* relying on a boot-time warmup that no longer exists.
|
|
19
|
+
*/
|
|
20
|
+
const HIGHLIGHT_LANGS = [
|
|
21
|
+
'typescript',
|
|
22
|
+
'tsx',
|
|
23
|
+
'javascript',
|
|
24
|
+
'jsx',
|
|
25
|
+
'json',
|
|
26
|
+
'markdown',
|
|
27
|
+
'css',
|
|
28
|
+
'bash',
|
|
29
|
+
'yaml',
|
|
30
|
+
'diff',
|
|
31
|
+
'python',
|
|
32
|
+
'go',
|
|
33
|
+
'rust',
|
|
34
|
+
'sql',
|
|
35
|
+
'html',
|
|
36
|
+
'php',
|
|
37
|
+
];
|
|
38
|
+
|
|
15
39
|
beforeAll(async () => {
|
|
16
40
|
// The isomorphic highlighter only boots with `window` present; the Bun test
|
|
17
41
|
// runtime has none. Stub it, boot, then remove the stub so other test files
|
|
@@ -19,6 +43,17 @@ beforeAll(async () => {
|
|
|
19
43
|
Object.assign(globalThis, { window: globalThis });
|
|
20
44
|
await bootSyntax();
|
|
21
45
|
Reflect.deleteProperty(globalThis, 'window');
|
|
46
|
+
|
|
47
|
+
await new Promise<void>((resolve) => {
|
|
48
|
+
const settle = () => {
|
|
49
|
+
if (HIGHLIGHT_LANGS.every((lang) => requestLanguage(lang))) {
|
|
50
|
+
off();
|
|
51
|
+
resolve();
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const off = onLanguageReady(settle);
|
|
55
|
+
settle();
|
|
56
|
+
});
|
|
22
57
|
});
|
|
23
58
|
|
|
24
59
|
describe('highlightCode', () => {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { HighlighterCore } from 'shiki/core';
|
|
2
|
+
|
|
3
|
+
import { escapeCode, getHighlighterSync, requestLanguage } from '@/shared/lib/code/highlighter';
|
|
2
4
|
import { SHIKI_THEMES } from '@/shared/lib/code/shiki-themes';
|
|
3
5
|
|
|
4
6
|
/** Map file extension to Shiki language id */
|
|
@@ -124,12 +126,24 @@ export function isCodeLike(text: string): boolean {
|
|
|
124
126
|
return codePatterns.some((pattern) => pattern.test(trimmed));
|
|
125
127
|
}
|
|
126
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Pick a language that can be tokenized *synchronously* right now. A grammar
|
|
131
|
+
* that has not arrived yet is requested here (fire-and-forget) and the caller
|
|
132
|
+
* falls back to javascript for this pass — `onLanguageReady` re-runs the
|
|
133
|
+
* highlight once it lands. `getLoadedLanguages()` stays the authority for
|
|
134
|
+
* embedded/alias names that `LANG_LOADERS` does not list by id.
|
|
135
|
+
*/
|
|
136
|
+
function resolveReadyLang(hl: HighlighterCore, language: string): string {
|
|
137
|
+
if (requestLanguage(language) || hl.getLoadedLanguages().includes(language)) return language;
|
|
138
|
+
return 'javascript';
|
|
139
|
+
}
|
|
140
|
+
|
|
127
141
|
/** Syntax highlight code string safely with fallback */
|
|
128
142
|
export function highlightCode(code: string, language = 'javascript'): string {
|
|
129
143
|
if (!code) return '';
|
|
130
144
|
const hl = getHighlighterSync();
|
|
131
145
|
if (!hl) return escapeCode(code);
|
|
132
|
-
const lang = hl
|
|
146
|
+
const lang = resolveReadyLang(hl, language);
|
|
133
147
|
try {
|
|
134
148
|
return `<span class="shiki">${hl.codeToHtml(code, {
|
|
135
149
|
lang,
|
|
@@ -264,7 +278,7 @@ export function highlightLines(code: string, language = 'javascript'): string[]
|
|
|
264
278
|
if (!code) return rawLines;
|
|
265
279
|
const hl = getHighlighterSync();
|
|
266
280
|
if (!hl) return rawLines.map(escapeCode);
|
|
267
|
-
const lang = hl
|
|
281
|
+
const lang = resolveReadyLang(hl, language);
|
|
268
282
|
try {
|
|
269
283
|
const { tokens } = hl.codeToTokens(code, {
|
|
270
284
|
lang,
|
|
@@ -26,6 +26,25 @@ export function parseTodayLabel(raw: string): number | null {
|
|
|
26
26
|
return Number.isNaN(parsed.getTime()) ? null : parsed.getTime();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Timeline stamp for a message row / run footer: `"Today, 10:30 AM"` for the
|
|
31
|
+
* live path, `"Sep 9, 04:43 AM"` for historical rows, empty when unparseable.
|
|
32
|
+
*/
|
|
33
|
+
export function formatMessageStamp(msg?: { date?: string; timestamp?: string }): string {
|
|
34
|
+
const raw = msg?.timestamp || msg?.date;
|
|
35
|
+
if (!raw) return '';
|
|
36
|
+
if (raw.startsWith('Today,')) return `Today, ${raw.slice('Today,'.length).trim()}`;
|
|
37
|
+
// Bare "10:30 AM" timestamp (live path) — resolve against today.
|
|
38
|
+
if (/^\d{1,2}:\d{2}\s*(AM|PM)$/i.test(raw)) {
|
|
39
|
+
const parsedMs = parseTodayLabel(`Today, ${raw}`);
|
|
40
|
+
if (parsedMs !== null) return `Today, ${formatClock(parsedMs)}`;
|
|
41
|
+
}
|
|
42
|
+
const date = new Date(raw);
|
|
43
|
+
if (Number.isNaN(date.getTime())) return '';
|
|
44
|
+
const day = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
45
|
+
return `${day}, ${formatClock(date)}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
29
48
|
/**
|
|
30
49
|
* Best-effort tool-detail timestamp: epoch-ms renders as the runtime's default
|
|
31
50
|
* locale time, a string passes through, anything else renders empty.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path formatting for the Files panel's copy actions.
|
|
3
|
+
*
|
|
4
|
+
* `/api/fs/dir` emits every entry as a path relative to the listing's base
|
|
5
|
+
* directory — the workspace root, or the selected nested repo — and reports
|
|
6
|
+
* that base back as `root`. Copy Path rebuilds the absolute path from those
|
|
7
|
+
* two halves; a relative path on its own is not resolvable outside the panel
|
|
8
|
+
* (the client-side `rootPath` may be `~`-relative, rejected by the server, or
|
|
9
|
+
* absent entirely in mock mode).
|
|
10
|
+
*/
|
|
11
|
+
export function toAbsolutePath(baseDir: string | null | undefined, relPath: string): string {
|
|
12
|
+
// `/`- and `./`-prefixed raws are stripped; a dot-file name (`.env`) is not a prefix.
|
|
13
|
+
const rel = relPath.replace(/^(?:\.?[/\\])+/, '').replace(/[/\\]+$/, '');
|
|
14
|
+
const base = (baseDir ?? '').replace(/[/\\]+$/, '');
|
|
15
|
+
if (!base) return rel;
|
|
16
|
+
if (!rel) return base;
|
|
17
|
+
// Join with the base's own separator: `C:\proj` + `src` → `C:\proj\src`.
|
|
18
|
+
return `${base}${base.includes('\\') && !base.includes('/') ? '\\' : '/'}${rel}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* GENERATED — do not edit by hand. Run `bun run scripts/katex-fonts-woff2.mjs`
|
|
8
|
+
* after upgrading @fontsource/fira-code.
|
|
9
|
+
*
|
|
10
|
+
* Fira Code, latin subset, woff2-only, weights 400/500/600/700 — the faces
|
|
11
|
+
* `main.tsx` imports. Replaces `@fontsource/fira-code/{400,500,600,700}.css`,
|
|
12
|
+
* which pulled 7 subsets (cyrillic, cyrillic-ext, greek, greek-ext, latin,
|
|
13
|
+
* latin-ext, symbols2) x 4 weights x 2 formats = 56 files. This console is
|
|
14
|
+
* English-only, so the other six subsets were dead weight, and the woff
|
|
15
|
+
* fallback is never fetched by a browser that supports woff2.
|
|
16
|
+
*
|
|
17
|
+
* `font-display: swap` and the family/style/weight declarations match upstream,
|
|
18
|
+
* so text metrics are unchanged.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
@font-face {
|
|
22
|
+
font-family: 'Fira Code';
|
|
23
|
+
font-style: normal;
|
|
24
|
+
font-display: swap;
|
|
25
|
+
font-weight: 400;
|
|
26
|
+
src: url('@fontsource/fira-code/files/fira-code-latin-400-normal.woff2') format('woff2');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@font-face {
|
|
30
|
+
font-family: 'Fira Code';
|
|
31
|
+
font-style: normal;
|
|
32
|
+
font-display: swap;
|
|
33
|
+
font-weight: 500;
|
|
34
|
+
src: url('@fontsource/fira-code/files/fira-code-latin-500-normal.woff2') format('woff2');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@font-face {
|
|
38
|
+
font-family: 'Fira Code';
|
|
39
|
+
font-style: normal;
|
|
40
|
+
font-display: swap;
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
src: url('@fontsource/fira-code/files/fira-code-latin-600-normal.woff2') format('woff2');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@font-face {
|
|
46
|
+
font-family: 'Fira Code';
|
|
47
|
+
font-style: normal;
|
|
48
|
+
font-display: swap;
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
src: url('@fontsource/fira-code/files/fira-code-latin-700-normal.woff2') format('woff2');
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* GENERATED — do not edit by hand. Run `bun run scripts/katex-fonts-woff2.mjs`
|
|
8
|
+
* after upgrading katex.
|
|
9
|
+
*
|
|
10
|
+
* KaTeX stylesheet, woff2-only. Upstream `katex/dist/katex.min.css` lists three
|
|
11
|
+
* sources per @font-face (woff2 -> woff -> truetype); a browser downloads only
|
|
12
|
+
* the first format it supports, so the woff/truetype files were never
|
|
13
|
+
* *requested* while still being emitted into dist/client/static/font
|
|
14
|
+
* (634 kB .woff + 502 kB .ttf). Every src chain below keeps the woff2 source
|
|
15
|
+
* only — the layout rules are byte-identical to upstream.
|
|
16
|
+
*
|
|
17
|
+
* woff2 is supported by Safari 10+, Chrome 36+, Firefox 39+, and Edge 14+;
|
|
18
|
+
* this app targets modern browsers, so the legacy fallbacks buy nothing.
|
|
19
|
+
*/
|
|
20
|
+
@font-face{font-display:block;font-family:KaTeX_AMS;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_AMS-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:700;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Caligraphic-Bold.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Caligraphic;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Caligraphic-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:700;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Fraktur-Bold.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Fraktur;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Fraktur-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:700;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Main-Bold.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:700;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Main-BoldItalic.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:italic;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Main-Italic.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Main;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Main-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:700;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Math-BoldItalic.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Math;font-style:italic;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Math-Italic.woff2") format("woff2")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:normal;font-weight:700;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_SansSerif-Bold.woff2") format("woff2")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:italic;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_SansSerif-Italic.woff2") format("woff2")}@font-face{font-display:block;font-family:"KaTeX_SansSerif";font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_SansSerif-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Script;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Script-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Size1;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Size1-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Size2;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Size2-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Size3;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Size3-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Size4;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Size4-Regular.woff2") format("woff2")}@font-face{font-display:block;font-family:KaTeX_Typewriter;font-style:normal;font-weight:400;src:url("../../../../node_modules/katex/dist/fonts/KaTeX_Typewriter-Regular.woff2") format("woff2")}.katex{font:normal 1.21em KaTeX_Main,Times New Roman,serif;line-height:1.2;position:relative;text-indent:0;text-rendering:auto}.katex *{-ms-high-contrast-adjust:none!important;border-color:currentColor}.katex .katex-version:after{content:"0.18.7"}.katex .katex-mathml{border:0;-webkit-clip-path:inset(50%);clip-path:inset(50%);height:1px;overflow:hidden;padding:0;position:absolute;width:1px}.katex .katex-html>.katex-newline{display:block}.katex .katex-base{position:relative;white-space:nowrap;width:-webkit-min-content;width:-moz-min-content;width:min-content}.katex .katex-base,.katex .katex-strut{display:inline-block}.katex .textbf{font-weight:700}.katex .textit{font-style:italic}.katex .textrm{font-family:KaTeX_Main}.katex .textsf{font-family:KaTeX_SansSerif}.katex .texttt{font-family:KaTeX_Typewriter}.katex .mathnormal{font-family:KaTeX_Math;font-style:italic}.katex .mathit{font-family:KaTeX_Main;font-style:italic}.katex .mathrm{font-style:normal}.katex .mathbf{font-family:KaTeX_Main;font-weight:700}.katex .boldsymbol{font-family:KaTeX_Math;font-style:italic;font-weight:700}.katex .amsrm,.katex .mathbb,.katex .textbb{font-family:KaTeX_AMS}.katex .mathcal{font-family:KaTeX_Caligraphic}.katex .mathfrak,.katex .textfrak{font-family:KaTeX_Fraktur}.katex .mathboldfrak,.katex .textboldfrak{font-family:KaTeX_Fraktur;font-weight:700}.katex .mathtt{font-family:KaTeX_Typewriter}.katex .mathscr,.katex .textscr{font-family:KaTeX_Script}.katex .mathsf,.katex .textsf{font-family:KaTeX_SansSerif}.katex .mathboldsf,.katex .textboldsf{font-family:KaTeX_SansSerif;font-weight:700}.katex .mathitsf,.katex .mathsfit,.katex .textitsf{font-family:KaTeX_SansSerif;font-style:italic}.katex .mainrm{font-family:KaTeX_Main;font-style:normal}.katex .vlist-t{border-collapse:collapse;display:inline-table;table-layout:fixed}.katex .vlist-r{display:table-row}.katex .vlist{display:table-cell;position:relative;vertical-align:bottom}.katex .vlist>span{display:block;height:0;position:relative}.katex .vlist>span>span{display:inline-block}.katex .vlist>span>.pstrut{overflow:hidden;width:0}.katex .vlist-t2{margin-right:-2px}.katex .vlist-s{display:table-cell;font-size:1px;min-width:2px;vertical-align:bottom;width:2px}.katex .katex-vbox{align-items:baseline;display:inline-flex;flex-direction:column}.katex .katex-thinbox{display:inline-flex;flex-direction:row;max-width:0;width:0}.katex .msupsub{text-align:left}.katex .mfrac>span>span{text-align:center}.katex .mfrac .frac-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .katex-hdashline,.katex .katex-hline,.katex .katex-overline .overline-line,.katex .katex-rule,.katex .katex-underline .underline-line,.katex .mfrac .frac-line{min-height:1px}.katex .mspace{display:inline-block}.katex .katex-smash{display:inline;line-height:0}.katex .clap,.katex .llap,.katex .rlap{position:relative;width:0}.katex .clap>.katex-inner,.katex .llap>.katex-inner,.katex .rlap>.katex-inner{position:absolute}.katex .clap>.katex-fix,.katex .llap>.katex-fix,.katex .rlap>.katex-fix{display:inline-block}.katex .llap>.katex-inner{right:0}.katex .clap>.katex-inner,.katex .rlap>.katex-inner{left:0}.katex .clap>.katex-inner>span{margin-left:-50%;margin-right:50%}.katex .katex-rule{border:0 solid;display:inline-block;position:relative}.katex .katex-hline,.katex .katex-overline .overline-line,.katex .katex-underline .underline-line{border-bottom-style:solid;display:inline-block;width:100%}.katex .katex-hdashline{border-bottom-style:dashed;display:inline-block;width:100%}.katex .sqrt>.katex-root{margin-left:.2777777778em;margin-right:-.5555555556em}.katex .fontsize-ensurer.reset-size1.size1,.katex .katex-sizing.reset-size1.size1{font-size:1em}.katex .fontsize-ensurer.reset-size1.size2,.katex .katex-sizing.reset-size1.size2{font-size:1.2em}.katex .fontsize-ensurer.reset-size1.size3,.katex .katex-sizing.reset-size1.size3{font-size:1.4em}.katex .fontsize-ensurer.reset-size1.size4,.katex .katex-sizing.reset-size1.size4{font-size:1.6em}.katex .fontsize-ensurer.reset-size1.size5,.katex .katex-sizing.reset-size1.size5{font-size:1.8em}.katex .fontsize-ensurer.reset-size1.size6,.katex .katex-sizing.reset-size1.size6{font-size:2em}.katex .fontsize-ensurer.reset-size1.size7,.katex .katex-sizing.reset-size1.size7{font-size:2.4em}.katex .fontsize-ensurer.reset-size1.size8,.katex .katex-sizing.reset-size1.size8{font-size:2.88em}.katex .fontsize-ensurer.reset-size1.size9,.katex .katex-sizing.reset-size1.size9{font-size:3.456em}.katex .fontsize-ensurer.reset-size1.size10,.katex .katex-sizing.reset-size1.size10{font-size:4.148em}.katex .fontsize-ensurer.reset-size1.size11,.katex .katex-sizing.reset-size1.size11{font-size:4.976em}.katex .fontsize-ensurer.reset-size2.size1,.katex .katex-sizing.reset-size2.size1{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size2.size2,.katex .katex-sizing.reset-size2.size2{font-size:1em}.katex .fontsize-ensurer.reset-size2.size3,.katex .katex-sizing.reset-size2.size3{font-size:1.1666666667em}.katex .fontsize-ensurer.reset-size2.size4,.katex .katex-sizing.reset-size2.size4{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size2.size5,.katex .katex-sizing.reset-size2.size5{font-size:1.5em}.katex .fontsize-ensurer.reset-size2.size6,.katex .katex-sizing.reset-size2.size6{font-size:1.6666666667em}.katex .fontsize-ensurer.reset-size2.size7,.katex .katex-sizing.reset-size2.size7{font-size:2em}.katex .fontsize-ensurer.reset-size2.size8,.katex .katex-sizing.reset-size2.size8{font-size:2.4em}.katex .fontsize-ensurer.reset-size2.size9,.katex .katex-sizing.reset-size2.size9{font-size:2.88em}.katex .fontsize-ensurer.reset-size2.size10,.katex .katex-sizing.reset-size2.size10{font-size:3.4566666667em}.katex .fontsize-ensurer.reset-size2.size11,.katex .katex-sizing.reset-size2.size11{font-size:4.1466666667em}.katex .fontsize-ensurer.reset-size3.size1,.katex .katex-sizing.reset-size3.size1{font-size:.7142857143em}.katex .fontsize-ensurer.reset-size3.size2,.katex .katex-sizing.reset-size3.size2{font-size:.8571428571em}.katex .fontsize-ensurer.reset-size3.size3,.katex .katex-sizing.reset-size3.size3{font-size:1em}.katex .fontsize-ensurer.reset-size3.size4,.katex .katex-sizing.reset-size3.size4{font-size:1.1428571429em}.katex .fontsize-ensurer.reset-size3.size5,.katex .katex-sizing.reset-size3.size5{font-size:1.2857142857em}.katex .fontsize-ensurer.reset-size3.size6,.katex .katex-sizing.reset-size3.size6{font-size:1.4285714286em}.katex .fontsize-ensurer.reset-size3.size7,.katex .katex-sizing.reset-size3.size7{font-size:1.7142857143em}.katex .fontsize-ensurer.reset-size3.size8,.katex .katex-sizing.reset-size3.size8{font-size:2.0571428571em}.katex .fontsize-ensurer.reset-size3.size9,.katex .katex-sizing.reset-size3.size9{font-size:2.4685714286em}.katex .fontsize-ensurer.reset-size3.size10,.katex .katex-sizing.reset-size3.size10{font-size:2.9628571429em}.katex .fontsize-ensurer.reset-size3.size11,.katex .katex-sizing.reset-size3.size11{font-size:3.5542857143em}.katex .fontsize-ensurer.reset-size4.size1,.katex .katex-sizing.reset-size4.size1{font-size:.625em}.katex .fontsize-ensurer.reset-size4.size2,.katex .katex-sizing.reset-size4.size2{font-size:.75em}.katex .fontsize-ensurer.reset-size4.size3,.katex .katex-sizing.reset-size4.size3{font-size:.875em}.katex .fontsize-ensurer.reset-size4.size4,.katex .katex-sizing.reset-size4.size4{font-size:1em}.katex .fontsize-ensurer.reset-size4.size5,.katex .katex-sizing.reset-size4.size5{font-size:1.125em}.katex .fontsize-ensurer.reset-size4.size6,.katex .katex-sizing.reset-size4.size6{font-size:1.25em}.katex .fontsize-ensurer.reset-size4.size7,.katex .katex-sizing.reset-size4.size7{font-size:1.5em}.katex .fontsize-ensurer.reset-size4.size8,.katex .katex-sizing.reset-size4.size8{font-size:1.8em}.katex .fontsize-ensurer.reset-size4.size9,.katex .katex-sizing.reset-size4.size9{font-size:2.16em}.katex .fontsize-ensurer.reset-size4.size10,.katex .katex-sizing.reset-size4.size10{font-size:2.5925em}.katex .fontsize-ensurer.reset-size4.size11,.katex .katex-sizing.reset-size4.size11{font-size:3.11em}.katex .fontsize-ensurer.reset-size5.size1,.katex .katex-sizing.reset-size5.size1{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size5.size2,.katex .katex-sizing.reset-size5.size2{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size5.size3,.katex .katex-sizing.reset-size5.size3{font-size:.7777777778em}.katex .fontsize-ensurer.reset-size5.size4,.katex .katex-sizing.reset-size5.size4{font-size:.8888888889em}.katex .fontsize-ensurer.reset-size5.size5,.katex .katex-sizing.reset-size5.size5{font-size:1em}.katex .fontsize-ensurer.reset-size5.size6,.katex .katex-sizing.reset-size5.size6{font-size:1.1111111111em}.katex .fontsize-ensurer.reset-size5.size7,.katex .katex-sizing.reset-size5.size7{font-size:1.3333333333em}.katex .fontsize-ensurer.reset-size5.size8,.katex .katex-sizing.reset-size5.size8{font-size:1.6em}.katex .fontsize-ensurer.reset-size5.size9,.katex .katex-sizing.reset-size5.size9{font-size:1.92em}.katex .fontsize-ensurer.reset-size5.size10,.katex .katex-sizing.reset-size5.size10{font-size:2.3044444444em}.katex .fontsize-ensurer.reset-size5.size11,.katex .katex-sizing.reset-size5.size11{font-size:2.7644444444em}.katex .fontsize-ensurer.reset-size6.size1,.katex .katex-sizing.reset-size6.size1{font-size:.5em}.katex .fontsize-ensurer.reset-size6.size2,.katex .katex-sizing.reset-size6.size2{font-size:.6em}.katex .fontsize-ensurer.reset-size6.size3,.katex .katex-sizing.reset-size6.size3{font-size:.7em}.katex .fontsize-ensurer.reset-size6.size4,.katex .katex-sizing.reset-size6.size4{font-size:.8em}.katex .fontsize-ensurer.reset-size6.size5,.katex .katex-sizing.reset-size6.size5{font-size:.9em}.katex .fontsize-ensurer.reset-size6.size6,.katex .katex-sizing.reset-size6.size6{font-size:1em}.katex .fontsize-ensurer.reset-size6.size7,.katex .katex-sizing.reset-size6.size7{font-size:1.2em}.katex .fontsize-ensurer.reset-size6.size8,.katex .katex-sizing.reset-size6.size8{font-size:1.44em}.katex .fontsize-ensurer.reset-size6.size9,.katex .katex-sizing.reset-size6.size9{font-size:1.728em}.katex .fontsize-ensurer.reset-size6.size10,.katex .katex-sizing.reset-size6.size10{font-size:2.074em}.katex .fontsize-ensurer.reset-size6.size11,.katex .katex-sizing.reset-size6.size11{font-size:2.488em}.katex .fontsize-ensurer.reset-size7.size1,.katex .katex-sizing.reset-size7.size1{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size7.size2,.katex .katex-sizing.reset-size7.size2{font-size:.5em}.katex .fontsize-ensurer.reset-size7.size3,.katex .katex-sizing.reset-size7.size3{font-size:.5833333333em}.katex .fontsize-ensurer.reset-size7.size4,.katex .katex-sizing.reset-size7.size4{font-size:.6666666667em}.katex .fontsize-ensurer.reset-size7.size5,.katex .katex-sizing.reset-size7.size5{font-size:.75em}.katex .fontsize-ensurer.reset-size7.size6,.katex .katex-sizing.reset-size7.size6{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size7.size7,.katex .katex-sizing.reset-size7.size7{font-size:1em}.katex .fontsize-ensurer.reset-size7.size8,.katex .katex-sizing.reset-size7.size8{font-size:1.2em}.katex .fontsize-ensurer.reset-size7.size9,.katex .katex-sizing.reset-size7.size9{font-size:1.44em}.katex .fontsize-ensurer.reset-size7.size10,.katex .katex-sizing.reset-size7.size10{font-size:1.7283333333em}.katex .fontsize-ensurer.reset-size7.size11,.katex .katex-sizing.reset-size7.size11{font-size:2.0733333333em}.katex .fontsize-ensurer.reset-size8.size1,.katex .katex-sizing.reset-size8.size1{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size8.size2,.katex .katex-sizing.reset-size8.size2{font-size:.4166666667em}.katex .fontsize-ensurer.reset-size8.size3,.katex .katex-sizing.reset-size8.size3{font-size:.4861111111em}.katex .fontsize-ensurer.reset-size8.size4,.katex .katex-sizing.reset-size8.size4{font-size:.5555555556em}.katex .fontsize-ensurer.reset-size8.size5,.katex .katex-sizing.reset-size8.size5{font-size:.625em}.katex .fontsize-ensurer.reset-size8.size6,.katex .katex-sizing.reset-size8.size6{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size8.size7,.katex .katex-sizing.reset-size8.size7{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size8.size8,.katex .katex-sizing.reset-size8.size8{font-size:1em}.katex .fontsize-ensurer.reset-size8.size9,.katex .katex-sizing.reset-size8.size9{font-size:1.2em}.katex .fontsize-ensurer.reset-size8.size10,.katex .katex-sizing.reset-size8.size10{font-size:1.4402777778em}.katex .fontsize-ensurer.reset-size8.size11,.katex .katex-sizing.reset-size8.size11{font-size:1.7277777778em}.katex .fontsize-ensurer.reset-size9.size1,.katex .katex-sizing.reset-size9.size1{font-size:.2893518519em}.katex .fontsize-ensurer.reset-size9.size2,.katex .katex-sizing.reset-size9.size2{font-size:.3472222222em}.katex .fontsize-ensurer.reset-size9.size3,.katex .katex-sizing.reset-size9.size3{font-size:.4050925926em}.katex .fontsize-ensurer.reset-size9.size4,.katex .katex-sizing.reset-size9.size4{font-size:.462962963em}.katex .fontsize-ensurer.reset-size9.size5,.katex .katex-sizing.reset-size9.size5{font-size:.5208333333em}.katex .fontsize-ensurer.reset-size9.size6,.katex .katex-sizing.reset-size9.size6{font-size:.5787037037em}.katex .fontsize-ensurer.reset-size9.size7,.katex .katex-sizing.reset-size9.size7{font-size:.6944444444em}.katex .fontsize-ensurer.reset-size9.size8,.katex .katex-sizing.reset-size9.size8{font-size:.8333333333em}.katex .fontsize-ensurer.reset-size9.size9,.katex .katex-sizing.reset-size9.size9{font-size:1em}.katex .fontsize-ensurer.reset-size9.size10,.katex .katex-sizing.reset-size9.size10{font-size:1.2002314815em}.katex .fontsize-ensurer.reset-size9.size11,.katex .katex-sizing.reset-size9.size11{font-size:1.4398148148em}.katex .fontsize-ensurer.reset-size10.size1,.katex .katex-sizing.reset-size10.size1{font-size:.2410800386em}.katex .fontsize-ensurer.reset-size10.size2,.katex .katex-sizing.reset-size10.size2{font-size:.2892960463em}.katex .fontsize-ensurer.reset-size10.size3,.katex .katex-sizing.reset-size10.size3{font-size:.337512054em}.katex .fontsize-ensurer.reset-size10.size4,.katex .katex-sizing.reset-size10.size4{font-size:.3857280617em}.katex .fontsize-ensurer.reset-size10.size5,.katex .katex-sizing.reset-size10.size5{font-size:.4339440694em}.katex .fontsize-ensurer.reset-size10.size6,.katex .katex-sizing.reset-size10.size6{font-size:.4821600771em}.katex .fontsize-ensurer.reset-size10.size7,.katex .katex-sizing.reset-size10.size7{font-size:.5785920926em}.katex .fontsize-ensurer.reset-size10.size8,.katex .katex-sizing.reset-size10.size8{font-size:.6943105111em}.katex .fontsize-ensurer.reset-size10.size9,.katex .katex-sizing.reset-size10.size9{font-size:.8331726133em}.katex .fontsize-ensurer.reset-size10.size10,.katex .katex-sizing.reset-size10.size10{font-size:1em}.katex .fontsize-ensurer.reset-size10.size11,.katex .katex-sizing.reset-size10.size11{font-size:1.1996142719em}.katex .fontsize-ensurer.reset-size11.size1,.katex .katex-sizing.reset-size11.size1{font-size:.2009646302em}.katex .fontsize-ensurer.reset-size11.size2,.katex .katex-sizing.reset-size11.size2{font-size:.2411575563em}.katex .fontsize-ensurer.reset-size11.size3,.katex .katex-sizing.reset-size11.size3{font-size:.2813504823em}.katex .fontsize-ensurer.reset-size11.size4,.katex .katex-sizing.reset-size11.size4{font-size:.3215434084em}.katex .fontsize-ensurer.reset-size11.size5,.katex .katex-sizing.reset-size11.size5{font-size:.3617363344em}.katex .fontsize-ensurer.reset-size11.size6,.katex .katex-sizing.reset-size11.size6{font-size:.4019292605em}.katex .fontsize-ensurer.reset-size11.size7,.katex .katex-sizing.reset-size11.size7{font-size:.4823151125em}.katex .fontsize-ensurer.reset-size11.size8,.katex .katex-sizing.reset-size11.size8{font-size:.578778135em}.katex .fontsize-ensurer.reset-size11.size9,.katex .katex-sizing.reset-size11.size9{font-size:.6945337621em}.katex .fontsize-ensurer.reset-size11.size10,.katex .katex-sizing.reset-size11.size10{font-size:.8336012862em}.katex .fontsize-ensurer.reset-size11.size11,.katex .katex-sizing.reset-size11.size11{font-size:1em}.katex .delimsizing.size1{font-family:KaTeX_Size1}.katex .delimsizing.size2{font-family:KaTeX_Size2}.katex .delimsizing.size3{font-family:KaTeX_Size3}.katex .delimsizing.size4{font-family:KaTeX_Size4}.katex .delimsizing.mult .delim-size1>span{font-family:KaTeX_Size1}.katex .delimsizing.mult .delim-size4>span{font-family:KaTeX_Size4}.katex .nulldelimiter{display:inline-block;width:.12em}.katex .delimcenter,.katex .op-symbol{position:relative}.katex .op-symbol.small-op{font-family:KaTeX_Size1}.katex .op-symbol.large-op{font-family:KaTeX_Size2}.katex .katex-accent>.vlist-t,.katex .op-limits>.vlist-t{text-align:center}.katex .katex-accent .accent-body{position:relative}.katex .katex-accent .accent-body:not(.accent-full){width:0}.katex .katex-overlay{display:block}.katex .mtable .vertical-separator{display:inline-block;min-width:1px}.katex .mtable .arraycolsep{display:inline-block}.katex .mtable .col-align-c>.vlist-t{text-align:center}.katex .mtable .col-align-l>.vlist-t{text-align:left}.katex .mtable .col-align-r>.vlist-t{text-align:right}.katex .svg-align{text-align:left}.katex svg{fill:currentColor;stroke:currentColor;display:block;height:inherit;position:absolute;width:100%}.katex svg path{stroke:none}.katex svg{fill-rule:nonzero;fill-opacity:1;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1}.katex img{border-style:none;max-height:none;max-width:none;min-height:0;min-width:0}.katex .katex-stretchy{display:block;overflow:hidden;position:relative;width:100%}.katex .katex-stretchy:after,.katex .katex-stretchy:before{content:""}.katex .hide-tail{overflow:hidden;position:relative;width:100%}.katex .halfarrow-left{left:0;overflow:hidden;position:absolute;width:50.2%}.katex .halfarrow-right{overflow:hidden;position:absolute;right:0;width:50.2%}.katex .brace-left{left:0;overflow:hidden;position:absolute;width:25.1%}.katex .brace-center{left:25%;overflow:hidden;position:absolute;width:50%}.katex .brace-right{overflow:hidden;position:absolute;right:0;width:25.1%}.katex .x-arrow-pad{padding:0 .5em}.katex .cd-arrow-pad{padding:0 .55556em 0 .27778em}.katex .mover,.katex .munder,.katex .x-arrow{text-align:center}.katex .boxpad{padding:0 .3em}.katex .fbox,.katex .fcolorbox{border:.04em solid;box-sizing:border-box}.katex .cancel-pad{padding:0 .2em}.katex .cancel-lap{margin-left:-.2em;margin-right:-.2em}.katex .katex-sout{border-bottom-style:solid;border-bottom-width:.08em}.katex .angl{border-right:.049em solid;border-top:.049em solid;box-sizing:border-box;margin-right:.03889em}.katex .anglpad{padding:0 .03889em}.katex .reflectbox{display:inline-block;transform:scaleX(-1)}.katex .eqn-num:before{content:"(" counter(katexEqnNo) ")";counter-increment:katexEqnNo}.katex .mml-eqn-num:before{content:"(" counter(mmlEqnNo) ")";counter-increment:mmlEqnNo}.katex .mtr-glue{width:50%}.katex .cd-vert-arrow{display:inline-block;position:relative}.katex .cd-label-left{display:inline-block;position:absolute;right:calc(50% + .3em);text-align:left}.katex .cd-label-right{display:inline-block;left:calc(50% + .3em);position:absolute;text-align:right}.katex-display{display:block;margin:1em 0;text-align:center}.katex-display>.katex{display:block;text-align:center;white-space:nowrap}.katex-display>.katex>.katex-html{display:block;position:relative}.katex-display>.katex>.katex-html>.katex-tag{position:absolute;right:0}.katex-display.leqno>.katex>.katex-html>.katex-tag{left:0;right:auto}.katex-display.fleqn>.katex{padding-left:2em;text-align:left}body{counter-reset:katexEqnNo mmlEqnNo}
|