ikanban-web 0.2.7 → 0.2.9
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/dist/assets/{ghostty-web-BnnMBzi5.js → ghostty-web-CveYg7XI.js} +1 -1
- package/dist/assets/{home-DeLBKxk3.js → home-OHGVA1hY.js} +1 -1
- package/dist/assets/{index-pnX-hTQP.css → index-B0TEjAod.css} +1 -1
- package/dist/assets/{index-ehO8js0v.js → index-BlcL7wgr.js} +132 -124
- package/dist/assets/session-CdIG8nTN.js +24 -0
- package/dist/index.html +19 -6
- package/package.json +1 -1
- package/src/components/dialog-session-timeline.tsx +85 -0
- package/src/components/prompt-input.tsx +3 -3
- package/src/components/settings-general.tsx +18 -93
- package/src/context/global-sync/child-store.ts +1 -0
- package/src/context/global-sync/event-reducer.test.ts +5 -0
- package/src/context/global-sync/event-reducer.ts +263 -183
- package/src/context/global-sync/types.ts +3 -0
- package/src/context/highlights.tsx +4 -176
- package/src/context/layout.tsx +12 -2
- package/src/context/sync.tsx +237 -2
- package/src/i18n/en.ts +11 -0
- package/src/i18n/zh.ts +22 -0
- package/src/pages/layout.tsx +11 -54
- package/src/pages/session/review-tab.tsx +7 -1
- package/src/pages/session/session-side-panel.tsx +17 -51
- package/src/pages/session/use-session-commands.tsx +91 -33
- package/src/pages/session.tsx +99 -16
- package/dist/assets/session-D7vMISoz.js +0 -24
package/dist/index.html
CHANGED
|
@@ -3,19 +3,32 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
-
<title>
|
|
7
|
-
<link
|
|
6
|
+
<title>IKanban</title>
|
|
7
|
+
<link
|
|
8
|
+
rel="icon"
|
|
9
|
+
type="image/png"
|
|
10
|
+
href="/ikanban/favicon-96x96-v3.png"
|
|
11
|
+
sizes="96x96"
|
|
12
|
+
/>
|
|
8
13
|
<link rel="icon" type="image/svg+xml" href="/ikanban/favicon-v3.svg" />
|
|
9
14
|
<link rel="shortcut icon" href="/ikanban/favicon-v3.ico" />
|
|
10
|
-
<link
|
|
15
|
+
<link
|
|
16
|
+
rel="apple-touch-icon"
|
|
17
|
+
sizes="180x180"
|
|
18
|
+
href="/ikanban/apple-touch-icon-v3.png"
|
|
19
|
+
/>
|
|
11
20
|
<link rel="manifest" href="/ikanban/site.webmanifest" />
|
|
12
21
|
<meta name="theme-color" content="#F8F7F7" />
|
|
13
|
-
<meta
|
|
22
|
+
<meta
|
|
23
|
+
name="theme-color"
|
|
24
|
+
content="#131010"
|
|
25
|
+
media="(prefers-color-scheme: dark)"
|
|
26
|
+
/>
|
|
14
27
|
<meta property="og:image" content="/ikanban/social-share.png" />
|
|
15
28
|
<meta property="twitter:image" content="/social-share.png" />
|
|
16
29
|
<script id="oc-theme-preload-script" src="/ikanban/oc-theme-preload.js"></script>
|
|
17
|
-
<script type="module" crossorigin src="/ikanban/assets/index-
|
|
18
|
-
<link rel="stylesheet" crossorigin href="/ikanban/assets/index-
|
|
30
|
+
<script type="module" crossorigin src="/ikanban/assets/index-BlcL7wgr.js"></script>
|
|
31
|
+
<link rel="stylesheet" crossorigin href="/ikanban/assets/index-B0TEjAod.css">
|
|
19
32
|
</head>
|
|
20
33
|
<body class="antialiased overscroll-none text-12-regular overflow-hidden">
|
|
21
34
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
package/package.json
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Component, createMemo } from "solid-js"
|
|
2
|
+
import { useParams } from "@solidjs/router"
|
|
3
|
+
import { useSync } from "@/context/sync"
|
|
4
|
+
import { useLanguage } from "@/context/language"
|
|
5
|
+
import { Dialog } from "ikanban-ui/dialog"
|
|
6
|
+
import { List } from "ikanban-ui/list"
|
|
7
|
+
import type { TextPart as SDKTextPart, UserMessage } from "@opencode-ai/sdk/v2/client"
|
|
8
|
+
|
|
9
|
+
interface TimelineMessage {
|
|
10
|
+
id: string
|
|
11
|
+
text: string
|
|
12
|
+
time: string
|
|
13
|
+
message?: UserMessage
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function formatTime(date: Date): string {
|
|
17
|
+
return date.toLocaleTimeString(undefined, { timeStyle: "short" })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DialogSessionTimeline: Component<{
|
|
21
|
+
onSelect: (message: UserMessage | undefined) => void
|
|
22
|
+
}> = (props) => {
|
|
23
|
+
const params = useParams()
|
|
24
|
+
const sync = useSync()
|
|
25
|
+
const language = useLanguage()
|
|
26
|
+
|
|
27
|
+
const messages = createMemo((): TimelineMessage[] => {
|
|
28
|
+
const sessionID = params.id
|
|
29
|
+
if (!sessionID) return []
|
|
30
|
+
|
|
31
|
+
const revert = sync.session.get(sessionID)?.revert?.messageID
|
|
32
|
+
const sessionMessages = sync.data.message[sessionID] ?? []
|
|
33
|
+
const result: TimelineMessage[] = []
|
|
34
|
+
let latestUserMessage: UserMessage | undefined
|
|
35
|
+
|
|
36
|
+
for (const message of sessionMessages) {
|
|
37
|
+
if (message.role !== "user") continue
|
|
38
|
+
latestUserMessage = message
|
|
39
|
+
|
|
40
|
+
const parts = sync.data.part[message.id] ?? []
|
|
41
|
+
const textPart = parts.find((x): x is SDKTextPart => x.type === "text" && !x.synthetic && !x.ignored)
|
|
42
|
+
if (!textPart) continue
|
|
43
|
+
|
|
44
|
+
result.push({
|
|
45
|
+
id: message.id,
|
|
46
|
+
text: textPart.text.replace(/\n/g, " ").slice(0, 200),
|
|
47
|
+
time: formatTime(new Date(message.time.created)),
|
|
48
|
+
message,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
result.reverse()
|
|
53
|
+
|
|
54
|
+
if (revert && latestUserMessage) {
|
|
55
|
+
result.unshift({
|
|
56
|
+
id: "latest",
|
|
57
|
+
text: language.t("dialog.timeline.latest"),
|
|
58
|
+
time: formatTime(new Date(latestUserMessage.time.created)),
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Dialog title={language.t("command.session.timeline")}>
|
|
67
|
+
<List
|
|
68
|
+
class="flex-1 min-h-0 [&_[data-slot=list-scroll]]:flex-1 [&_[data-slot=list-scroll]]:min-h-0"
|
|
69
|
+
search={{ placeholder: language.t("common.search.placeholder"), autofocus: true }}
|
|
70
|
+
emptyMessage={language.t("dialog.timeline.empty")}
|
|
71
|
+
key={(item) => item.id}
|
|
72
|
+
items={messages}
|
|
73
|
+
filterKeys={["text"]}
|
|
74
|
+
onSelect={(item) => props.onSelect(item?.message)}
|
|
75
|
+
>
|
|
76
|
+
{(item) => (
|
|
77
|
+
<div class="w-full flex items-center gap-2">
|
|
78
|
+
<span class="truncate flex-1 min-w-0 text-left font-normal">{item.text}</span>
|
|
79
|
+
<span class="text-text-weak shrink-0 font-normal">{item.time}</span>
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</List>
|
|
83
|
+
</Dialog>
|
|
84
|
+
)
|
|
85
|
+
}
|
|
@@ -162,9 +162,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|
|
162
162
|
const sessionID = params.id
|
|
163
163
|
if (!sessionID) return false
|
|
164
164
|
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
return
|
|
165
|
+
const sessionDiffs = sync.data.session_diff[sessionID] ?? []
|
|
166
|
+
const projectDiffs = sync.data.project_diff[sync.directory] ?? []
|
|
167
|
+
return [...sessionDiffs, ...projectDiffs].some((diff) => diff.file === path)
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
const openComment = (item: { path: string; commentID?: string; commentOrigin?: "review" | "file" }) => {
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { Component, Show, createMemo, createResource, type JSX } from "solid-js"
|
|
2
|
-
import { createStore } from "solid-js/store"
|
|
3
2
|
import { Button } from "ikanban-ui/button"
|
|
4
3
|
import { Icon } from "ikanban-ui/icon"
|
|
5
4
|
import { Select } from "ikanban-ui/select"
|
|
6
5
|
import { Switch } from "ikanban-ui/switch"
|
|
7
6
|
import { Tooltip } from "ikanban-ui/tooltip"
|
|
8
7
|
import { useTheme, type ColorScheme } from "ikanban-ui/theme"
|
|
9
|
-
import { showToast } from "ikanban-ui/toast"
|
|
10
8
|
import { useLanguage } from "@/context/language"
|
|
11
9
|
import { usePlatform } from "@/context/platform"
|
|
12
10
|
import { useSettings, monoFontFamily } from "@/context/settings"
|
|
@@ -38,69 +36,23 @@ const playDemoSound = (src: string | undefined) => {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
export const SettingsGeneral: Component = () => {
|
|
39
|
+
const CHANGELOG_URL = "https://github.com/isomoes/ikanban/blob/main/CHANGELOG.md"
|
|
40
|
+
|
|
41
41
|
const theme = useTheme()
|
|
42
42
|
const language = useLanguage()
|
|
43
43
|
const platform = usePlatform()
|
|
44
44
|
const settings = useSettings()
|
|
45
45
|
|
|
46
|
-
const [store, setStore] = createStore({
|
|
47
|
-
checking: false,
|
|
48
|
-
})
|
|
49
|
-
|
|
50
46
|
const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux")
|
|
51
47
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
showToast({
|
|
61
|
-
variant: "success",
|
|
62
|
-
icon: "circle-check",
|
|
63
|
-
title: language.t("settings.updates.toast.latest.title"),
|
|
64
|
-
description: language.t("settings.updates.toast.latest.description", { version: platform.version ?? "" }),
|
|
65
|
-
})
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const actions =
|
|
70
|
-
platform.update && platform.restart
|
|
71
|
-
? [
|
|
72
|
-
{
|
|
73
|
-
label: language.t("toast.update.action.installRestart"),
|
|
74
|
-
onClick: async () => {
|
|
75
|
-
await platform.update!()
|
|
76
|
-
await platform.restart!()
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
label: language.t("toast.update.action.notYet"),
|
|
81
|
-
onClick: "dismiss" as const,
|
|
82
|
-
},
|
|
83
|
-
]
|
|
84
|
-
: [
|
|
85
|
-
{
|
|
86
|
-
label: language.t("toast.update.action.notYet"),
|
|
87
|
-
onClick: "dismiss" as const,
|
|
88
|
-
},
|
|
89
|
-
]
|
|
90
|
-
|
|
91
|
-
showToast({
|
|
92
|
-
persistent: true,
|
|
93
|
-
icon: "download",
|
|
94
|
-
title: language.t("toast.update.title"),
|
|
95
|
-
description: language.t("toast.update.description", { version: result.version ?? "" }),
|
|
96
|
-
actions,
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
.catch((err: unknown) => {
|
|
100
|
-
const message = err instanceof Error ? err.message : String(err)
|
|
101
|
-
showToast({ title: language.t("common.requestFailed"), description: message })
|
|
102
|
-
})
|
|
103
|
-
.finally(() => setStore("checking", false))
|
|
48
|
+
const openCurrentVersionChanges = () => {
|
|
49
|
+
const normalized = platform.version?.trim().replace(/^v/i, "")
|
|
50
|
+
if (!normalized) {
|
|
51
|
+
platform.openLink(CHANGELOG_URL)
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
const fragment = normalized.replace(/\./g, "")
|
|
55
|
+
platform.openLink(`${CHANGELOG_URL}#${fragment}`)
|
|
104
56
|
}
|
|
105
57
|
|
|
106
58
|
const themeOptions = createMemo(() =>
|
|
@@ -412,44 +364,17 @@ export const SettingsGeneral: Component = () => {
|
|
|
412
364
|
</div>
|
|
413
365
|
)
|
|
414
366
|
|
|
415
|
-
const
|
|
367
|
+
const ReleaseNotesSection = () => (
|
|
416
368
|
<div class="flex flex-col gap-1">
|
|
417
|
-
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.
|
|
369
|
+
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.releaseNotes")}</h3>
|
|
418
370
|
|
|
419
371
|
<div class="bg-surface-raised-base px-4 rounded-lg">
|
|
420
372
|
<SettingsRow
|
|
421
|
-
title={language.t("settings.
|
|
422
|
-
description={language.t("settings.
|
|
423
|
-
>
|
|
424
|
-
<div data-action="settings-updates-startup">
|
|
425
|
-
<Switch
|
|
426
|
-
checked={settings.updates.startup()}
|
|
427
|
-
disabled={!platform.checkUpdate}
|
|
428
|
-
onChange={(checked) => settings.updates.setStartup(checked)}
|
|
429
|
-
/>
|
|
430
|
-
</div>
|
|
431
|
-
</SettingsRow>
|
|
432
|
-
|
|
433
|
-
<SettingsRow
|
|
434
|
-
title={language.t("settings.general.row.releaseNotes.title")}
|
|
435
|
-
description={language.t("settings.general.row.releaseNotes.description")}
|
|
373
|
+
title={language.t("settings.general.row.releaseNotesCurrent.title")}
|
|
374
|
+
description={language.t("settings.general.row.releaseNotesCurrent.description")}
|
|
436
375
|
>
|
|
437
|
-
<
|
|
438
|
-
|
|
439
|
-
checked={settings.general.releaseNotes()}
|
|
440
|
-
onChange={(checked) => settings.general.setReleaseNotes(checked)}
|
|
441
|
-
/>
|
|
442
|
-
</div>
|
|
443
|
-
</SettingsRow>
|
|
444
|
-
|
|
445
|
-
<SettingsRow
|
|
446
|
-
title={language.t("settings.updates.row.check.title")}
|
|
447
|
-
description={language.t("settings.updates.row.check.description")}
|
|
448
|
-
>
|
|
449
|
-
<Button size="small" variant="secondary" disabled={store.checking || !platform.checkUpdate} onClick={check}>
|
|
450
|
-
{store.checking
|
|
451
|
-
? language.t("settings.updates.action.checking")
|
|
452
|
-
: language.t("settings.updates.action.checkNow")}
|
|
376
|
+
<Button size="small" variant="secondary" onClick={openCurrentVersionChanges}>
|
|
377
|
+
{language.t("settings.general.action.releaseNotesCurrent")}
|
|
453
378
|
</Button>
|
|
454
379
|
</SettingsRow>
|
|
455
380
|
</div>
|
|
@@ -473,6 +398,8 @@ export const SettingsGeneral: Component = () => {
|
|
|
473
398
|
|
|
474
399
|
<SoundsSection />
|
|
475
400
|
|
|
401
|
+
<ReleaseNotesSection />
|
|
402
|
+
|
|
476
403
|
{/*<Show when={platform.platform === "desktop" && platform.os === "windows" && platform.getWslEnabled}>
|
|
477
404
|
{(_) => {
|
|
478
405
|
const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.())
|
|
@@ -501,8 +428,6 @@ export const SettingsGeneral: Component = () => {
|
|
|
501
428
|
}}
|
|
502
429
|
</Show>*/}
|
|
503
430
|
|
|
504
|
-
<UpdatesSection />
|
|
505
|
-
|
|
506
431
|
<Show when={linux()}>
|
|
507
432
|
{(_) => {
|
|
508
433
|
const [valueResource, actions] = createResource(() => platform.getDisplayBackend?.())
|
|
@@ -72,6 +72,7 @@ const baseState = (input: Partial<State> = {}) =>
|
|
|
72
72
|
sessionTotal: 0,
|
|
73
73
|
session_status: {},
|
|
74
74
|
session_diff: {},
|
|
75
|
+
project_diff: {},
|
|
75
76
|
todo: {},
|
|
76
77
|
permission: {},
|
|
77
78
|
question: {},
|
|
@@ -174,6 +175,7 @@ describe("applyDirectoryEvent", () => {
|
|
|
174
175
|
message: { ses_1: [message] },
|
|
175
176
|
part: { [message.id]: [textPart("prt_1", "ses_1", message.id)] },
|
|
176
177
|
session_diff: { ses_1: [] },
|
|
178
|
+
project_diff: { "/tmp": [] },
|
|
177
179
|
todo: { ses_1: [] },
|
|
178
180
|
permission: { ses_1: [] },
|
|
179
181
|
question: { ses_1: [] },
|
|
@@ -195,6 +197,7 @@ describe("applyDirectoryEvent", () => {
|
|
|
195
197
|
expect(store.message.ses_1).toBeUndefined()
|
|
196
198
|
expect(store.part[message.id]).toBeUndefined()
|
|
197
199
|
expect(store.session_diff.ses_1).toBeUndefined()
|
|
200
|
+
expect(store.project_diff["/tmp"]).toEqual([])
|
|
198
201
|
expect(store.todo.ses_1).toBeUndefined()
|
|
199
202
|
expect(store.permission.ses_1).toBeUndefined()
|
|
200
203
|
expect(store.question.ses_1).toBeUndefined()
|
|
@@ -220,6 +223,7 @@ describe("applyDirectoryEvent", () => {
|
|
|
220
223
|
message: { [item.info.id]: [message] },
|
|
221
224
|
part: { [message.id]: [textPart("prt_1", item.info.id, message.id)] },
|
|
222
225
|
session_diff: { [item.info.id]: [] },
|
|
226
|
+
project_diff: { "/tmp": [] },
|
|
223
227
|
todo: { [item.info.id]: [] },
|
|
224
228
|
permission: { [item.info.id]: [] },
|
|
225
229
|
question: { [item.info.id]: [] },
|
|
@@ -241,6 +245,7 @@ describe("applyDirectoryEvent", () => {
|
|
|
241
245
|
expect(store.message[item.info.id]).toBeUndefined()
|
|
242
246
|
expect(store.part[message.id]).toBeUndefined()
|
|
243
247
|
expect(store.session_diff[item.info.id]).toBeUndefined()
|
|
248
|
+
expect(store.project_diff["/tmp"]).toEqual([])
|
|
244
249
|
expect(store.todo[item.info.id]).toBeUndefined()
|
|
245
250
|
expect(store.permission[item.info.id]).toBeUndefined()
|
|
246
251
|
expect(store.question[item.info.id]).toBeUndefined()
|