ompchamber 1.2.1 → 2.0.1
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 +143 -147
- package/dist/client/index.html +4 -2
- package/dist/client/static/css/index.5f630421b7.css +2 -0
- package/dist/client/static/js/77.0d81c74cf0.js +139 -0
- package/dist/client/static/js/async/1k.973e8e21fc.js +2 -0
- package/dist/client/static/js/async/{3y.3186ecdd63.js → 3y.3530b824ad.js} +6 -6
- package/dist/client/static/js/async/{q.b453193538.js → 4m.928a1dde85.js} +10 -10
- package/dist/client/static/js/async/66.5dfa29f957.js +93 -0
- package/dist/client/static/js/async/7g.a7814350c3.js +1 -0
- package/dist/client/static/js/async/ar.9303e5f2e8.js +2 -0
- package/dist/client/static/js/async/{cx.9c8268d542.js → cx.3cb0242a3a.js} +1 -1
- package/dist/client/static/js/async/e.93214d622b.js +79 -0
- package/dist/client/static/js/async/g.cb81980498.js +2 -0
- package/dist/client/static/js/async/h1.393f066d09.js +1 -0
- package/dist/client/static/js/async/jl.874d352278.js +1 -0
- package/dist/client/static/js/async/{k.d16cd09ae7.js → k.3210286401.js} +1 -1
- package/dist/client/static/js/async/{m.2e1ea3db9e.js → m.eb2e6752e2.js} +3 -3
- package/dist/client/static/js/async/m.eb2e6752e2.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/mt.0352e976d8.js +1 -0
- package/dist/client/static/js/async/{oc.eb22093f05.js → oc.b73042e8bd.js} +1 -1
- package/dist/client/static/js/async/{og.53f068804c.js → og.8eb577893a.js} +1 -1
- package/dist/client/static/js/async/px.fc6877924b.js +1 -0
- package/dist/client/static/js/async/q.22588845a9.js +21 -0
- package/dist/client/static/js/async/{q5.88b50ffa95.js → q5.97dc1481b6.js} +1 -1
- package/dist/client/static/js/async/s.0f395cdda5.js +94 -0
- package/dist/client/static/js/async/{tr.2a0d2b7600.js → tr.e878658140.js} +1 -1
- package/dist/client/static/js/async/vt.42416b707d.js +1 -0
- package/dist/client/static/js/async/x1.da8687159b.js +90 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js +2 -0
- package/dist/client/static/js/async/xe.da8dcc2015.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/{xn.6360379f71.js → xn.6510c0abe2.js} +2 -2
- package/dist/client/static/js/async/{yx.af2936c368.js → xn1.83a7784fe1.js} +3 -3
- package/dist/client/static/js/async/xn1.83a7784fe1.js.LICENSE.txt +11 -0
- package/dist/client/static/js/async/yg.35bebd6d0c.js +149 -0
- package/dist/client/static/js/async/yv.d99d829136.js +1 -0
- package/dist/client/static/js/index.1fd9e1c543.js +38 -0
- package/package.json +3 -3
- package/src/client/components/common/ComposerTextarea.tsx +9 -1
- package/src/client/components/common/MarkdownRenderer.tsx +50 -12
- package/src/client/components/mobile/mobile-right-sidebar/FullEditor.tsx +9 -3
- package/src/client/components/settings/Modal.tsx +11 -2
- package/src/client/components/settings/categories/AppearanceSettings.tsx +7 -30
- package/src/client/components/settings/categories/ProviderSettings.tsx +2 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeCard.tsx +61 -0
- package/src/client/components/settings/categories/appearance-settings/ThemeGrid.tsx +80 -0
- package/src/client/components/settings/categories/chat-settings/TitleSection.tsx +1 -1
- package/src/client/components/settings/categories/provider-settings/AddProviderModal.tsx +43 -6
- package/src/client/components/settings/categories/provider-settings/ModelConfigModal.tsx +54 -54
- package/src/client/components/workspace/SearchPanel.tsx +16 -3
- package/src/client/components/workspace/btw-panel/AskCard.tsx +11 -6
- package/src/client/components/workspace/btw-panel/Messages.tsx +105 -51
- package/src/client/components/workspace/btw-panel/index.tsx +124 -45
- package/src/client/components/workspace/chat-timeline/AiMessageFooter.tsx +30 -14
- package/src/client/components/workspace/chat-timeline/ComposerDock.tsx +10 -1
- package/src/client/components/workspace/chat-timeline/GeneratingIndicator.tsx +8 -2
- package/src/client/components/workspace/chat-timeline/MessageItem.tsx +27 -16
- package/src/client/components/workspace/chat-timeline/MessageList.tsx +11 -0
- package/src/client/components/workspace/chat-timeline/RunFooter.tsx +8 -0
- package/src/client/components/workspace/chat-timeline/chat-input/index.tsx +12 -1
- package/src/client/components/workspace/chat-timeline/index.tsx +7 -2
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/QuestionBlock.tsx +42 -15
- package/src/client/components/workspace/chat-timeline/tool-renderers/ask-panel/index.tsx +13 -1
- package/src/client/components/workspace/chat-timeline/tool-renderers/extension-dialog/index.tsx +13 -4
- package/src/client/components/workspace/diff-panel/SplitView.tsx +4 -1
- package/src/client/components/workspace/diff-panel/UnifiedView.tsx +70 -65
- package/src/client/components/workspace/diff-panel/index.tsx +8 -1
- package/src/client/components/workspace/editor/index.tsx +18 -2
- package/src/client/components/workspace/file-explorer/GitRepoDropdown.tsx +15 -41
- package/src/client/components/workspace/file-explorer/index.tsx +86 -16
- package/src/client/components/workspace/git-panel/index.tsx +60 -78
- package/src/client/components/workspace/terminal-panel/Header.tsx +16 -1
- package/src/client/components/workspace/terminal-panel/RealtimeXtermView.tsx +5 -5
- package/src/client/components/workspace/terminal-panel/index.tsx +6 -2
- package/src/client/components/workspace/terminal-panel/mount.ts +2 -2
- package/src/client/data/theme/terminal.ts +50 -52
- package/src/client/hooks/chat/btw/index.ts +139 -18
- package/src/client/hooks/chat/btw/intercept.ts +8 -8
- package/src/client/hooks/chat/btw/mode.ts +111 -16
- package/src/client/hooks/chat/composer/index.ts +10 -4
- package/src/client/hooks/chat/timeline/access-mode.ts +41 -0
- package/src/client/hooks/chat/timeline/ask-frames.test.ts +87 -0
- package/src/client/hooks/chat/timeline/ask-frames.ts +20 -1
- package/src/client/hooks/chat/timeline/auto-scroll.ts +13 -4
- package/src/client/hooks/chat/timeline/index.ts +8 -20
- package/src/client/hooks/chat/timeline/queue.ts +66 -9
- package/src/client/hooks/chat/timeline/scroll.test.ts +260 -0
- package/src/client/hooks/chat/timeline/scroll.ts +93 -20
- package/src/client/hooks/chat/timeline/session-load.ts +7 -1
- package/src/client/hooks/chat/timeline/session-pagination.test.ts +167 -0
- package/src/client/hooks/chat/timeline/session-pagination.ts +37 -11
- package/src/client/hooks/editor/save-file.ts +62 -0
- package/src/client/hooks/editor/use-file-editor.test.ts +61 -5
- package/src/client/hooks/editor/use-file-editor.ts +38 -29
- package/src/client/hooks/editor/use-file-transfer.ts +5 -2
- package/src/client/hooks/settings/providers.ts +34 -3
- package/src/client/hooks/ui/open-file.ts +7 -1
- package/src/client/hooks/ui/theme.ts +82 -57
- package/src/client/hooks/workspace/file-tabs.ts +5 -1
- package/src/client/hooks/workspace/repo-scope/index.ts +119 -0
- package/src/client/hooks/workspace/repo-scope/store.test.ts +161 -0
- package/src/client/hooks/workspace/repo-scope/store.ts +201 -0
- package/src/client/hooks/workspace/search-stream.ts +24 -5
- package/src/client/main.tsx +6 -0
- package/src/client/tailwind.css +19 -56
- package/src/server/lib/btw/child.server.ts +41 -0
- package/src/server/lib/btw/lifecycle.server.ts +254 -0
- package/src/server/lib/btw/prompt.ts +13 -5
- package/src/server/lib/btw/registry.server.ts +13 -4
- package/src/server/lib/btw/runtime.server.ts +149 -156
- package/src/server/lib/btw/schema.server.ts +27 -0
- package/src/server/lib/btw/service.server.ts +106 -5
- package/src/server/lib/btw/store.server.ts +57 -6
- package/src/server/lib/db/schema.ts +4 -0
- package/src/server/lib/db/settings-store.test.ts +65 -0
- package/src/server/lib/db/settings-store.ts +25 -0
- package/src/server/lib/fs/atomic-write.ts +42 -4
- package/src/server/lib/fs/config-lock.ts +74 -0
- package/src/server/lib/fs/git-ignore.ts +55 -5
- package/src/server/lib/fs/ripgrep.ts +119 -0
- package/src/server/lib/lifecycle/identity.ts +10 -64
- package/src/server/lib/lifecycle/proc/darwin.ts +238 -0
- package/src/server/lib/lifecycle/proc/index.ts +38 -0
- package/src/server/lib/lifecycle/proc/linux.ts +80 -0
- package/src/server/lib/lifecycle/proc/ps.ts +86 -0
- package/src/server/lib/lifecycle/proc/types.ts +33 -0
- package/src/server/lib/lifecycle/proc/win32.ts +53 -0
- package/src/server/lib/models/provider-registry.server.ts +2 -2
- package/src/server/lib/models/registry.server.ts +1 -1
- package/src/server/lib/omp/config/behavior.ts +17 -17
- package/src/server/lib/omp/config/disabled-providers.ts +84 -35
- package/src/server/lib/omp/config/document.ts +128 -0
- package/src/server/lib/omp/config/extensions.ts +11 -10
- package/src/server/lib/omp/config/mcp.ts +4 -57
- package/src/server/lib/omp/config/model-overrides.ts +123 -0
- package/src/server/lib/omp/config/models-config.ts +92 -0
- package/src/server/lib/omp/config/models-validation.ts +159 -0
- package/src/server/lib/omp/config/path-scoped.ts +74 -0
- package/src/server/lib/omp/config/providers.ts +241 -211
- package/src/server/lib/omp/config/roles.ts +6 -14
- package/src/server/lib/omp/core/auth-credentials.ts +1 -1
- package/src/server/lib/omp/rpc/frame-fold.test.ts +124 -0
- package/src/server/lib/omp/rpc/frame-fold.ts +43 -24
- package/src/server/lib/omp/rpc/lifecycle.test.ts +41 -0
- package/src/server/lib/omp/rpc/manager.ts +21 -21
- package/src/server/lib/omp/rpc/pending-ui-dialogs.ts +11 -7
- package/src/server/lib/omp/rpc/session-commands.ts +17 -29
- package/src/server/lib/omp/rpc/session-registry.ts +5 -66
- package/src/server/lib/omp/rpc/web-state.ts +0 -6
- package/src/server/lib/omp/session/auto-title.server.test.ts +109 -11
- package/src/server/lib/omp/session/auto-title.server.ts +95 -20
- package/src/server/lib/omp/session/sidebar-data.server.ts +11 -6
- package/src/server/lib/queue/delivery-dispatch.server.test.ts +162 -0
- package/src/server/lib/queue/delivery-harness.ts +88 -0
- package/src/server/lib/queue/delivery.server.test.ts +78 -149
- package/src/server/lib/queue/delivery.server.ts +105 -13
- package/src/server/lib/queue/store.server.ts +30 -5
- package/src/server/lib/terminal/foreground.test.ts +164 -0
- package/src/server/lib/terminal/foreground.ts +34 -6
- package/src/server/lib/terminal/history.ts +8 -7
- package/src/server/plugins/compress.ts +33 -5
- package/src/server/plugins/ssr.ts +14 -7
- package/src/server/routes/btw/topics.ts +89 -2
- package/src/server/routes/fs/action.test.ts +64 -0
- package/src/server/routes/fs/action.ts +10 -1
- package/src/server/routes/fs/git.ts +5 -6
- package/src/server/routes/fs/search.ts +17 -26
- package/src/server/routes/sessions/queue.ts +17 -4
- package/src/server/routes/settings/index.ts +2 -0
- package/src/server/routes/settings/model-override.ts +69 -0
- package/src/server/routes/settings/provider-models.ts +35 -12
- package/src/server/routes/settings/providers.ts +31 -1
- package/src/server/routes/settings/root.ts +8 -3
- package/src/shared/lib/chat/btw/client.ts +34 -0
- package/src/shared/lib/chat/timeline/tool-phrases.ts +7 -0
- package/src/shared/lib/code/line-endings.test.ts +38 -0
- package/src/shared/lib/code/line-endings.ts +56 -0
- package/src/shared/lib/code/shiki-themes.ts +9 -5
- package/src/shared/lib/fs/file-tree.ts +17 -12
- package/src/shared/lib/markdown/autolink.test.ts +32 -0
- package/src/shared/lib/markdown/autolink.ts +50 -0
- package/src/shared/lib/markdown/document-urls.test.ts +68 -0
- package/src/shared/lib/markdown/document-urls.ts +118 -0
- package/src/shared/lib/markdown/marked.ts +27 -15
- package/src/shared/lib/markdown/mermaid.ts +2 -2
- package/src/shared/lib/models/catalog.ts +1 -1
- package/src/shared/lib/omp/session/schema-migrations.ts +30 -0
- package/src/shared/lib/omp/session/stream-state.server.test.ts +54 -0
- package/src/shared/lib/omp/session/stream-state.server.ts +70 -21
- package/src/shared/lib/settings/client.ts +10 -4
- package/src/shared/lib/settings/diff.test.ts +31 -0
- package/src/shared/lib/settings/diff.ts +37 -0
- package/src/shared/lib/theme/catalog.test.ts +168 -0
- package/src/shared/lib/theme/catalog.ts +98 -0
- package/src/shared/lib/theme/color.ts +88 -0
- package/src/shared/lib/theme/css.ts +70 -0
- package/src/shared/lib/theme/palettes/chamber.ts +61 -0
- package/src/shared/lib/theme/palettes/dark.ts +313 -0
- package/src/shared/lib/theme/palettes/light.ts +313 -0
- package/src/shared/types/btw.ts +83 -4
- package/src/shared/types/index.ts +1 -0
- package/src/shared/types/settings/provider.ts +8 -1
- package/src/shared/types/settings/state.ts +10 -4
- package/src/shared/types/theme.ts +48 -0
- package/dist/client/static/css/index.b938f4a693.css +0 -2
- package/dist/client/static/js/77.8022a800b5.js +0 -139
- package/dist/client/static/js/async/1k.a411fe5b43.js +0 -2
- package/dist/client/static/js/async/66.86544ad741.js +0 -93
- package/dist/client/static/js/async/7g.db82aca7ba.js +0 -1
- package/dist/client/static/js/async/ar.ad5c8822a8.js +0 -2
- package/dist/client/static/js/async/e.6443e739a2.js +0 -79
- package/dist/client/static/js/async/g.ee41bfed7f.js +0 -2
- package/dist/client/static/js/async/h1.d7091cef78.js +0 -1
- package/dist/client/static/js/async/jl.4911fa3383.js +0 -1
- package/dist/client/static/js/async/m.2e1ea3db9e.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/mt.853be2b5aa.js +0 -1
- package/dist/client/static/js/async/ot.84c71b308f.js +0 -2
- package/dist/client/static/js/async/px.783b6f91f2.js +0 -1
- package/dist/client/static/js/async/q8.7f1b48e9cd.js +0 -21
- package/dist/client/static/js/async/q8.7f1b48e9cd.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/s.aad2ecebc6.js +0 -94
- package/dist/client/static/js/async/vt.20406a5604.js +0 -1
- package/dist/client/static/js/async/x1.fe8af17016.js +0 -90
- package/dist/client/static/js/async/xn.6360379f71.js.LICENSE.txt +0 -6
- package/dist/client/static/js/async/yg.5a94e1fc8a.js +0 -149
- package/dist/client/static/js/async/yv.2fa22f009a.js +0 -1
- package/dist/client/static/js/index.5fe7a2cb64.js +0 -34
- package/src/client/components/workspace/btw-panel/Composer.tsx +0 -183
- package/src/client/components/workspace/btw-panel/attachments.ts +0 -80
- /package/dist/client/static/js/{77.8022a800b5.js.LICENSE.txt → 77.0d81c74cf0.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{g.ee41bfed7f.js.LICENSE.txt → 1k.973e8e21fc.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{3y.3186ecdd63.js.LICENSE.txt → 3y.3530b824ad.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ot.84c71b308f.js.LICENSE.txt → 4m.928a1dde85.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{q.b453193538.js.LICENSE.txt → ar.9303e5f2e8.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{yx.af2936c368.js.LICENSE.txt → g.cb81980498.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{1k.a411fe5b43.js.LICENSE.txt → q.22588845a9.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/async/{ar.ad5c8822a8.js.LICENSE.txt → xn.6510c0abe2.js.LICENSE.txt} +0 -0
- /package/dist/client/static/js/{index.5fe7a2cb64.js.LICENSE.txt → index.1fd9e1c543.js.LICENSE.txt} +0 -0
|
@@ -38,80 +38,85 @@ export function UnifiedView({ lines, language }: UnifiedViewProps) {
|
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
40
|
<div className="w-full h-full overflow-auto font-mono text-xs select-text bg-paper text-ink code-surface">
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
{/* The room under the last row belongs to the content, not the scroller:
|
|
42
|
+
a scroll container's own bottom padding is not part of its scrollable
|
|
43
|
+
overflow, so the padding has to sit inside it to have any effect. */}
|
|
44
|
+
<div className="pb-4">
|
|
45
|
+
<table className="w-max min-w-full border-collapse">
|
|
46
|
+
<tbody>
|
|
47
|
+
{lines.map((line, idx) => {
|
|
48
|
+
if (line.type === 'meta') {
|
|
49
|
+
return (
|
|
50
|
+
<tr key={idx} className="bg-canvas border-y border-ink/10 text-meta select-none">
|
|
51
|
+
<td className="w-12 text-center py-1 px-2 text-[10px] opacity-60 border-r border-ink/10 font-mono">...</td>
|
|
52
|
+
<td className="w-12 text-center py-1 px-2 text-[10px] opacity-60 border-r border-ink/10 font-mono">...</td>
|
|
53
|
+
<td className="py-1 px-3 text-[11px] font-semibold text-meta font-mono tracking-tight">{line.text}</td>
|
|
54
|
+
</tr>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
58
|
+
const isAdd = line.type === 'add';
|
|
59
|
+
const isDel = line.type === 'del';
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
isAdd
|
|
62
|
-
? 'bg-success/10 hover:bg-success/15'
|
|
63
|
-
: isDel
|
|
64
|
-
? 'bg-error/10 hover:bg-error/15'
|
|
65
|
-
: 'hover:bg-ink/[0.03]'
|
|
66
|
-
}`}
|
|
67
|
-
>
|
|
68
|
-
{/* Old line number */}
|
|
69
|
-
<td
|
|
70
|
-
className={`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${
|
|
71
|
-
isDel
|
|
72
|
-
? 'bg-error/15 text-error font-semibold'
|
|
73
|
-
: isAdd
|
|
74
|
-
? 'bg-success/15 text-success/70'
|
|
75
|
-
: 'bg-canvas/50 text-ink/40'
|
|
76
|
-
}`}
|
|
77
|
-
>
|
|
78
|
-
{line.oldLineNumber || ''}
|
|
79
|
-
</td>
|
|
80
|
-
|
|
81
|
-
{/* New line number */}
|
|
82
|
-
<td
|
|
83
|
-
className={`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${
|
|
61
|
+
return (
|
|
62
|
+
<tr
|
|
63
|
+
key={idx}
|
|
64
|
+
className={`transition-colors group ${
|
|
84
65
|
isAdd
|
|
85
|
-
? 'bg-success/
|
|
66
|
+
? 'bg-success/10 hover:bg-success/15'
|
|
86
67
|
: isDel
|
|
87
|
-
? 'bg-error/
|
|
88
|
-
: 'bg-
|
|
68
|
+
? 'bg-error/10 hover:bg-error/15'
|
|
69
|
+
: 'hover:bg-ink/[0.03]'
|
|
89
70
|
}`}
|
|
90
71
|
>
|
|
91
|
-
{line
|
|
92
|
-
|
|
72
|
+
{/* Old line number */}
|
|
73
|
+
<td
|
|
74
|
+
className={`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${
|
|
75
|
+
isDel
|
|
76
|
+
? 'bg-error/15 text-error font-semibold'
|
|
77
|
+
: isAdd
|
|
78
|
+
? 'bg-success/15 text-success/70'
|
|
79
|
+
: 'bg-canvas/50 text-ink/40'
|
|
80
|
+
}`}
|
|
81
|
+
>
|
|
82
|
+
{line.oldLineNumber || ''}
|
|
83
|
+
</td>
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
{/* New line number */}
|
|
86
|
+
<td
|
|
87
|
+
className={`w-12 py-0.5 px-2 text-right text-[10px] select-none border-r border-ink/10 font-mono ${
|
|
88
|
+
isAdd
|
|
89
|
+
? 'bg-success/15 text-success font-semibold'
|
|
90
|
+
: isDel
|
|
91
|
+
? 'bg-error/15 text-error/70'
|
|
92
|
+
: 'bg-canvas/50 text-ink/40'
|
|
99
93
|
}`}
|
|
100
94
|
>
|
|
101
|
-
{
|
|
102
|
-
</
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
95
|
+
{line.newLineNumber || ''}
|
|
96
|
+
</td>
|
|
97
|
+
|
|
98
|
+
{/* Change prefix & code text */}
|
|
99
|
+
<td className="py-0.5 px-3 whitespace-pre text-ink pr-6">
|
|
100
|
+
<span
|
|
101
|
+
className={`inline-block w-4 text-center select-none font-bold ${
|
|
102
|
+
isAdd ? 'text-success' : isDel ? 'text-error' : 'opacity-0'
|
|
103
|
+
}`}
|
|
104
|
+
>
|
|
105
|
+
{isAdd ? '+' : isDel ? '-' : ' '}
|
|
106
|
+
</span>
|
|
107
|
+
<span
|
|
108
|
+
className="shiki"
|
|
109
|
+
dangerouslySetInnerHTML={{
|
|
110
|
+
__html: line.text ? htmlByIndex[idx] || ' ' : ' ',
|
|
111
|
+
}}
|
|
112
|
+
/>
|
|
113
|
+
</td>
|
|
114
|
+
</tr>
|
|
115
|
+
);
|
|
116
|
+
})}
|
|
117
|
+
</tbody>
|
|
118
|
+
</table>
|
|
119
|
+
</div>
|
|
115
120
|
</div>
|
|
116
121
|
);
|
|
117
122
|
}
|
|
@@ -135,7 +135,14 @@ export function DiffPanel({
|
|
|
135
135
|
};
|
|
136
136
|
|
|
137
137
|
return (
|
|
138
|
-
|
|
138
|
+
// The panel fills, it does not size itself: the caller owns the height
|
|
139
|
+
// (`flex-1` in the editor's column, `h-full` in the mobile overlay). A
|
|
140
|
+
// hardcoded `h-full` here resolves against the whole editor column rather
|
|
141
|
+
// than the space left under the tabs, so the panel began below the tabs
|
|
142
|
+
// *and* stood the full column tall — its bottom, the toolbar's height,
|
|
143
|
+
// hung past the panel edge and was clipped for good, since the scroll
|
|
144
|
+
// range ends below the visible area.
|
|
145
|
+
<div className={`flex flex-col min-h-0 bg-paper select-text relative font-sans ${className}`}>
|
|
139
146
|
{/* Top Diff Toolbar */}
|
|
140
147
|
<DiffToolbar
|
|
141
148
|
filePath={filePath}
|
|
@@ -154,8 +154,17 @@ export function Editor({
|
|
|
154
154
|
) : (
|
|
155
155
|
<div onScroll={handleScroll} className={`flex-1 overflow-auto bg-paper flex ${scrollbarFadeClass(isScrolling)}`}>
|
|
156
156
|
{(isMd && isPreview) ? (
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
// No `prose` wrapper: markdown is styled by `.prose-content`
|
|
158
|
+
// alone (the same system the chat timeline uses). The
|
|
159
|
+
// typography plugin fought it — `.prose img` added 2em
|
|
160
|
+
// vertical margins that ballooned a badge row into its own
|
|
161
|
+
// line box, and `--tw-prose-*` colors ignored the theme.
|
|
162
|
+
<div className="p-6 max-w-4xl mx-auto font-sans flex-1" style={{ fontSize: `${zoomLevel}px` }}>
|
|
163
|
+
<MarkdownRenderer
|
|
164
|
+
content={currentContent}
|
|
165
|
+
document
|
|
166
|
+
scope={{ path: activeFile.path, root: activeFile.root, repo: activeFile.repo }}
|
|
167
|
+
/>
|
|
159
168
|
</div>
|
|
160
169
|
) : (
|
|
161
170
|
<CodeSurface
|
|
@@ -184,6 +193,13 @@ export function Editor({
|
|
|
184
193
|
fontSize: zoomLevel,
|
|
185
194
|
lineHeight: 1.5,
|
|
186
195
|
minHeight: '100%',
|
|
196
|
+
// Breathing room under the last line. It has to live on the
|
|
197
|
+
// surface, not on the scroll container: a scroll container's
|
|
198
|
+
// own bottom padding is not part of its scrollable overflow,
|
|
199
|
+
// so `pb-*` on the scroller left the last line flush with the
|
|
200
|
+
// panel's edge (measured: padding-bottom 32px did not change
|
|
201
|
+
// scrollHeight by a single pixel).
|
|
202
|
+
paddingBottom: 16,
|
|
187
203
|
}}
|
|
188
204
|
/>
|
|
189
205
|
)}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
|
|
2
|
-
import {
|
|
2
|
+
import { useRef, useState } from 'preact/hooks';
|
|
3
3
|
import { Check, ChevronDown, FolderGit2, RotateCcw, Search } from 'lucide-preact';
|
|
4
4
|
import { useOnClickOutside } from '@/client/hooks/ui/on-click-outside';
|
|
5
|
-
import { REPO_DISCOVERY_POLL_MS } from '@/shared/lib/workspace/refresh-cadence';
|
|
6
5
|
|
|
7
6
|
interface GitRepoDropdownProps {
|
|
8
7
|
rootPath?: string;
|
|
9
8
|
activeRepo: string;
|
|
10
9
|
onSelectRepo: (repo: string) => void;
|
|
10
|
+
/** Discovered repos of the active root; the panel owns discovery. */
|
|
11
|
+
repos: string[];
|
|
12
|
+
/** Discovery or a forced rescan is in flight. */
|
|
13
|
+
scanning: boolean;
|
|
14
|
+
onRefreshRepos: () => void;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
function workspaceFolderName(rootPath?: string): string {
|
|
@@ -16,51 +20,21 @@ function workspaceFolderName(rootPath?: string): string {
|
|
|
16
20
|
return parts.length > 0 ? parts[parts.length - 1] : 'workspace root';
|
|
17
21
|
}
|
|
18
22
|
|
|
19
|
-
export function GitRepoDropdown({
|
|
23
|
+
export function GitRepoDropdown({
|
|
24
|
+
rootPath,
|
|
25
|
+
activeRepo,
|
|
26
|
+
onSelectRepo,
|
|
27
|
+
repos,
|
|
28
|
+
scanning,
|
|
29
|
+
onRefreshRepos,
|
|
30
|
+
}: GitRepoDropdownProps) {
|
|
20
31
|
const [open, setOpen] = useState(false);
|
|
21
|
-
const [repos, setRepos] = useState<string[]>(['.']);
|
|
22
|
-
const [scanning, setScanning] = useState(false);
|
|
23
32
|
const [query, setQuery] = useState('');
|
|
24
33
|
const ref = useRef<HTMLDivElement>(null);
|
|
25
34
|
const rootLabel = workspaceFolderName(rootPath);
|
|
26
35
|
|
|
27
36
|
const label = (r: string) => (r === '.' ? rootLabel : r);
|
|
28
37
|
|
|
29
|
-
const fetchRepos = async (rescan = false) => {
|
|
30
|
-
const params = new URLSearchParams({ reposOnly: '1' });
|
|
31
|
-
if (rootPath) params.set('root', rootPath);
|
|
32
|
-
if (rescan) params.set('rescan', '1');
|
|
33
|
-
params.set('t', String(Date.now()));
|
|
34
|
-
const data = await fetch(`/api/fs/git?${params.toString()}`).then(r => r.json()).catch(() => null);
|
|
35
|
-
if (data && Array.isArray(data.repos)) {
|
|
36
|
-
setRepos(data.repos);
|
|
37
|
-
if (data.reposPending) {
|
|
38
|
-
setScanning(true);
|
|
39
|
-
} else {
|
|
40
|
-
setScanning(false);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (!open) return;
|
|
47
|
-
void fetchRepos();
|
|
48
|
-
}, [open, rootPath]);
|
|
49
|
-
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
if (!scanning) return;
|
|
52
|
-
const params = new URLSearchParams({ reposOnly: '1' });
|
|
53
|
-
if (rootPath) params.set('root', rootPath);
|
|
54
|
-
const id = setInterval(async () => {
|
|
55
|
-
const data = await fetch(`/api/fs/git?${params.toString()}`).then(r => r.json()).catch(() => null);
|
|
56
|
-
if (data && !data.reposPending && Array.isArray(data.repos)) {
|
|
57
|
-
setRepos(data.repos);
|
|
58
|
-
setScanning(false);
|
|
59
|
-
}
|
|
60
|
-
}, REPO_DISCOVERY_POLL_MS);
|
|
61
|
-
return () => clearInterval(id);
|
|
62
|
-
}, [scanning, rootPath]);
|
|
63
|
-
|
|
64
38
|
useOnClickOutside(ref, () => setOpen(false));
|
|
65
39
|
|
|
66
40
|
const filteredRepos = repos.filter(r => {
|
|
@@ -106,7 +80,7 @@ export function GitRepoDropdown({ rootPath, activeRepo, onSelectRepo }: GitRepoD
|
|
|
106
80
|
)}
|
|
107
81
|
<button
|
|
108
82
|
type="button"
|
|
109
|
-
onClick={
|
|
83
|
+
onClick={onRefreshRepos}
|
|
110
84
|
title="Refresh nested repos"
|
|
111
85
|
disabled={scanning}
|
|
112
86
|
className="text-ink/40 hover:text-ink flex-shrink-0 transition-colors disabled:opacity-40 disabled:hover:text-ink/40"
|
|
@@ -1,28 +1,71 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
2
|
import { RefreshCw, Search } from 'lucide-preact';
|
|
3
|
+
import type { FsNode } from '@/shared/types';
|
|
3
4
|
import { rehydrateTree, setChildrenAt } from '@/shared/lib/fs/file-tree';
|
|
5
|
+
import { isRecord } from '@/shared/lib/util/guards';
|
|
4
6
|
import { GitRepoDropdown } from '@/client/components/workspace/file-explorer/GitRepoDropdown';
|
|
5
7
|
import { FileTreeItem } from '@/client/components/workspace/file-explorer/TreeItem';
|
|
6
8
|
import { useScrollbarFade, scrollbarFadeClass } from '@/client/hooks/ui/scrollbar-fade';
|
|
7
9
|
import { useSessionState } from '@/client/hooks/workspace/session-state';
|
|
8
10
|
import { useGitStatus } from '@/client/hooks/workspace/git-status';
|
|
11
|
+
import { useRepoList, useRepoScope } from '@/client/hooks/workspace/repo-scope';
|
|
9
12
|
import { usePanelRefresh, useFileMutationRefresh } from '@/client/hooks/workspace/panel-refresh';
|
|
10
13
|
|
|
14
|
+
/**
|
|
15
|
+
* The listing on screen, tagged with the `root\0repo` scope it was read for.
|
|
16
|
+
*
|
|
17
|
+
* Tagging it is what makes a workspace switch instant and complete: the panel
|
|
18
|
+
* renders an empty tree until the new root's listing lands, instead of showing
|
|
19
|
+
* the previous workspace's files under the new workspace's header. It also
|
|
20
|
+
* covers what a plain "reset on switch" effect cannot — the children loaded on
|
|
21
|
+
* demand and the expansion set are keyed by paths RELATIVE to the listed root,
|
|
22
|
+
* so `src/` in one workspace would rehydrate into `src/` in the next.
|
|
23
|
+
*/
|
|
24
|
+
interface Listing {
|
|
25
|
+
scope: string;
|
|
26
|
+
files: FsNode[];
|
|
27
|
+
/** Absolute base dir reported by `/api/fs/dir`; Copy Path anchors on it. */
|
|
28
|
+
root: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const EMPTY_LISTING: Listing = { scope: '', files: [], root: '' };
|
|
32
|
+
|
|
33
|
+
/** Directory listing payload from `/api/fs/dir`, validated field by field. */
|
|
34
|
+
function readDirPayload(data: unknown): { files: FsNode[]; root: string } {
|
|
35
|
+
if (!isRecord(data)) return { files: [], root: '' };
|
|
36
|
+
return {
|
|
37
|
+
files: Array.isArray(data.files) ? data.files.filter((f): f is FsNode => isRecord(f)) : [],
|
|
38
|
+
root: typeof data.root === 'string' ? data.root : '',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
11
42
|
export function FileExplorer({ className = '', enabled = true, rootPath, onOpenFile, refreshKey = 0, onRefresh }: { className?: string, enabled?: boolean, rootPath?: string, onOpenFile?: (file: any) => void, refreshKey?: number, onRefresh?: () => void }) {
|
|
12
|
-
const [
|
|
13
|
-
// Absolute base dir reported by `/api/fs/dir` for the current listing — the
|
|
14
|
-
// workspace root, or the selected nested repo. Copy Path anchors on it
|
|
15
|
-
// because the client's `rootPath` may be rejected/unset.
|
|
16
|
-
const [listingRoot, setListingRoot] = useState('');
|
|
43
|
+
const [listing, setListing] = useState<Listing>(EMPTY_LISTING);
|
|
17
44
|
const [searchQuery, setSearchQuery] = useSessionState<string>('files.searchQuery', '');
|
|
18
45
|
const [isLoading, setIsLoading] = useState(false);
|
|
19
46
|
const [storedExpandedPaths, setStoredExpandedPaths, expandedPathsReady] = useSessionState<string[]>('files.expandedPaths', []);
|
|
20
47
|
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => new Set(storedExpandedPaths));
|
|
21
|
-
const childrenCacheRef = useRef<Record<string,
|
|
22
|
-
const
|
|
48
|
+
const childrenCacheRef = useRef<Record<string, FsNode[]>>({});
|
|
49
|
+
const { activeRepo, setActiveRepo } = useRepoScope(rootPath, 'files.activeRepo');
|
|
50
|
+
const { repos, scanning: reposScanning, rescan: rescanRepos } = useRepoList(rootPath, enabled);
|
|
23
51
|
const { isScrolling, handleScroll } = useScrollbarFade();
|
|
24
52
|
const { fileMap: gitFileMap, folderMap: gitFolderMap, refreshGitStatus } = useGitStatus(rootPath, activeRepo, refreshKey, enabled);
|
|
25
53
|
|
|
54
|
+
// The scope every request and every cached child path below belongs to. A
|
|
55
|
+
// change to it means the previous workspace's or repo's entries are not this
|
|
56
|
+
// one's, so nothing read under the old value may be rendered.
|
|
57
|
+
const scope = `${rootPath ?? ''}\u0000${activeRepo}`;
|
|
58
|
+
const scopeRef = useRef(scope);
|
|
59
|
+
scopeRef.current = scope;
|
|
60
|
+
// The scope of the listing on screen, read outside the render closure so the
|
|
61
|
+
// async loaders can tell a superseded tree from the current one.
|
|
62
|
+
const listedScopeRef = useRef(EMPTY_LISTING.scope);
|
|
63
|
+
|
|
64
|
+
const commitListing = (next: Listing) => {
|
|
65
|
+
listedScopeRef.current = next.scope;
|
|
66
|
+
setListing(next);
|
|
67
|
+
};
|
|
68
|
+
|
|
26
69
|
useEffect(() => {
|
|
27
70
|
if (!expandedPathsReady) return;
|
|
28
71
|
setExpandedPaths(prev => {
|
|
@@ -43,14 +86,23 @@ export function FileExplorer({ className = '', enabled = true, rootPath, onOpenF
|
|
|
43
86
|
|
|
44
87
|
const loadFiles = (opts?: { silent?: boolean }) => {
|
|
45
88
|
if (!enabled) return;
|
|
89
|
+
const requested = scopeRef.current;
|
|
46
90
|
if (!opts?.silent) setIsLoading(true);
|
|
47
91
|
fetch(listUrl())
|
|
48
92
|
.then(r => r.json())
|
|
49
93
|
.then(data => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
94
|
+
// A read that was superseded by a workspace switch describes a tree
|
|
95
|
+
// this panel is no longer showing.
|
|
96
|
+
if (scopeRef.current !== requested) return;
|
|
97
|
+
// Children cached under the previous tree are keyed by paths relative
|
|
98
|
+
// to a root that is not this one: drop them before rehydrating.
|
|
99
|
+
if (listedScopeRef.current !== requested) childrenCacheRef.current = {};
|
|
100
|
+
const payload = readDirPayload(data);
|
|
101
|
+
commitListing({
|
|
102
|
+
scope: requested,
|
|
103
|
+
files: rehydrateTree(payload.files, childrenCacheRef.current),
|
|
104
|
+
root: payload.root,
|
|
105
|
+
});
|
|
54
106
|
})
|
|
55
107
|
.catch(() => {})
|
|
56
108
|
.finally(() => {
|
|
@@ -85,13 +137,17 @@ export function FileExplorer({ className = '', enabled = true, rootPath, onOpenF
|
|
|
85
137
|
// so a freshly listed row never renders bare next to a status map that
|
|
86
138
|
// still predates its change (the panel poll alone can lag a fresh edit).
|
|
87
139
|
refreshGitStatus();
|
|
140
|
+
const requested = scopeRef.current;
|
|
88
141
|
return fetch(listUrl(path))
|
|
89
142
|
.then(r => r.json())
|
|
90
143
|
.then(data => {
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
144
|
+
if (scopeRef.current !== requested) return;
|
|
145
|
+
const payload = readDirPayload(data);
|
|
146
|
+
if (payload.files.length === 0 && payload.root === '') return;
|
|
147
|
+
childrenCacheRef.current[path] = payload.files;
|
|
148
|
+
setListing(prev => prev.scope === requested
|
|
149
|
+
? { ...prev, files: setChildrenAt(prev.files, path, payload.files) }
|
|
150
|
+
: prev);
|
|
95
151
|
})
|
|
96
152
|
.catch(() => {});
|
|
97
153
|
};
|
|
@@ -106,6 +162,10 @@ export function FileExplorer({ className = '', enabled = true, rootPath, onOpenF
|
|
|
106
162
|
|
|
107
163
|
const handleSelectRepo = (repo: string) => {
|
|
108
164
|
if (repo === activeRepo) return;
|
|
165
|
+
// The expansion set holds paths relative to the repo that was listed, so a
|
|
166
|
+
// repo switch starts from a collapsed tree. (A workspace switch needs no
|
|
167
|
+
// equivalent: the listing is re-read under the new root, and the session's
|
|
168
|
+
// own expansion set is restored with the session.)
|
|
109
169
|
childrenCacheRef.current = {};
|
|
110
170
|
setExpandedPaths(new Set());
|
|
111
171
|
setStoredExpandedPaths([]);
|
|
@@ -120,6 +180,9 @@ export function FileExplorer({ className = '', enabled = true, rootPath, onOpenF
|
|
|
120
180
|
);
|
|
121
181
|
}
|
|
122
182
|
|
|
183
|
+
const tree = listing.scope === scope ? listing.files : [];
|
|
184
|
+
const listingRoot = listing.scope === scope ? listing.root : '';
|
|
185
|
+
|
|
123
186
|
const getFilteredFiles = () => {
|
|
124
187
|
if (!searchQuery.trim()) return tree;
|
|
125
188
|
|
|
@@ -158,7 +221,14 @@ export function FileExplorer({ className = '', enabled = true, rootPath, onOpenF
|
|
|
158
221
|
<div className={`flex flex-col h-full bg-paper ${className}`}>
|
|
159
222
|
<div className="p-3 border-b border-ink/10 flex flex-col space-y-2">
|
|
160
223
|
<div className="flex items-center justify-between">
|
|
161
|
-
<GitRepoDropdown
|
|
224
|
+
<GitRepoDropdown
|
|
225
|
+
rootPath={rootPath}
|
|
226
|
+
activeRepo={activeRepo}
|
|
227
|
+
onSelectRepo={handleSelectRepo}
|
|
228
|
+
repos={repos}
|
|
229
|
+
scanning={reposScanning}
|
|
230
|
+
onRefreshRepos={rescanRepos}
|
|
231
|
+
/>
|
|
162
232
|
<button
|
|
163
233
|
onClick={refresh}
|
|
164
234
|
className="p-1.5 text-ink/40 hover:text-ink hover:bg-ink/5 rounded transition-colors"
|