ikanban-web 0.2.9 → 0.2.11

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/index.html CHANGED
@@ -27,8 +27,8 @@
27
27
  <meta property="og:image" content="/ikanban/social-share.png" />
28
28
  <meta property="twitter:image" content="/social-share.png" />
29
29
  <script id="oc-theme-preload-script" src="/ikanban/oc-theme-preload.js"></script>
30
- <script type="module" crossorigin src="/ikanban/assets/index-BlcL7wgr.js"></script>
31
- <link rel="stylesheet" crossorigin href="/ikanban/assets/index-B0TEjAod.css">
30
+ <script type="module" crossorigin src="/ikanban/assets/index-CE2ZqGRp.js"></script>
31
+ <link rel="stylesheet" crossorigin href="/ikanban/assets/index-Z4u1EVXA.css">
32
32
  </head>
33
33
  <body class="antialiased overscroll-none text-12-regular overflow-hidden">
34
34
  <noscript>You need to enable JavaScript to run this app.</noscript>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ikanban-web",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
4
4
  "description": "iKanban web app",
5
5
  "private": false,
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
32
  "@kobalte/core": "0.13.11",
33
- "@opencode-ai/sdk": "^1.2.16",
33
+ "@opencode-ai/sdk": "^1.2.21",
34
34
  "@shikijs/transformers": "4.0.1",
35
35
  "@solid-primitives/active-element": "2.1.5",
36
36
  "@solid-primitives/audio": "1.4.4",
package/src/app.tsx CHANGED
@@ -1,43 +1,54 @@
1
- import "@/index.css"
2
- import { File } from "ikanban-ui/file"
3
- import { I18nProvider } from "ikanban-ui/context"
4
- import { DialogProvider } from "ikanban-ui/context/dialog"
5
- import { FileComponentProvider } from "ikanban-ui/context/file"
6
- import { MarkedProvider } from "ikanban-ui/context/marked"
7
- import { Font } from "ikanban-ui/font"
8
- import { ThemeProvider } from "ikanban-ui/theme"
9
- import { MetaProvider } from "@solidjs/meta"
10
- import { Navigate, Route, Router } from "@solidjs/router"
11
- import { ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js"
12
- import { CommandProvider } from "@/context/command"
13
- import { CommentsProvider } from "@/context/comments"
14
- import { FileProvider } from "@/context/file"
15
- import { GlobalSDKProvider } from "@/context/global-sdk"
16
- import { GlobalSyncProvider } from "@/context/global-sync"
17
- import { HighlightsProvider } from "@/context/highlights"
18
- import { LanguageProvider, useLanguage } from "@/context/language"
19
- import { LayoutProvider } from "@/context/layout"
20
- import { ModelsProvider } from "@/context/models"
21
- import { NotificationProvider } from "@/context/notification"
22
- import { PermissionProvider } from "@/context/permission"
23
- import { usePlatform } from "@/context/platform"
24
- import { PromptProvider } from "@/context/prompt"
25
- import { type ServerConnection, ServerProvider, useServer } from "@/context/server"
26
- import { SettingsProvider } from "@/context/settings"
27
- import { TerminalProvider } from "@/context/terminal"
28
- import DirectoryLayout from "@/pages/directory-layout"
29
- import Layout from "@/pages/layout"
30
- import { ErrorPage } from "./pages/error"
1
+ import "@/index.css";
2
+ import { File } from "ikanban-ui/file";
3
+ import { I18nProvider } from "ikanban-ui/context";
4
+ import { DialogProvider } from "ikanban-ui/context/dialog";
5
+ import { FileComponentProvider } from "ikanban-ui/context/file";
6
+ import { MarkedProvider } from "ikanban-ui/context/marked";
7
+ import { Font } from "ikanban-ui/font";
8
+ import { ThemeProvider } from "ikanban-ui/theme";
9
+ import { MetaProvider } from "@solidjs/meta";
10
+ import { Route, Router } from "@solidjs/router";
11
+ import {
12
+ ErrorBoundary,
13
+ type JSX,
14
+ lazy,
15
+ type ParentProps,
16
+ Show,
17
+ Suspense,
18
+ } from "solid-js";
19
+ import { CommandProvider } from "@/context/command";
20
+ import { CommentsProvider } from "@/context/comments";
21
+ import { FileProvider } from "@/context/file";
22
+ import { GlobalSDKProvider } from "@/context/global-sdk";
23
+ import { GlobalSyncProvider } from "@/context/global-sync";
24
+ import { HighlightsProvider } from "@/context/highlights";
25
+ import { LanguageProvider, useLanguage } from "@/context/language";
26
+ import { LayoutProvider } from "@/context/layout";
27
+ import { ModelsProvider } from "@/context/models";
28
+ import { NotificationProvider } from "@/context/notification";
29
+ import { PermissionProvider } from "@/context/permission";
30
+ import { usePlatform } from "@/context/platform";
31
+ import { PromptProvider } from "@/context/prompt";
32
+ import {
33
+ type ServerConnection,
34
+ ServerProvider,
35
+ useServer,
36
+ } from "@/context/server";
37
+ import { SettingsProvider } from "@/context/settings";
38
+ import { TerminalProvider } from "@/context/terminal";
39
+ import DirectoryLayout from "@/pages/directory-layout";
40
+ import Layout from "@/pages/layout";
41
+ import { ErrorPage } from "./pages/error";
31
42
 
32
- const Home = lazy(() => import("@/pages/home"))
33
- const Session = lazy(() => import("@/pages/session"))
34
- const Loading = () => <div class="size-full" />
43
+ const Home = lazy(() => import("@/pages/home"));
44
+ const Session = lazy(() => import("@/pages/session"));
45
+ const Loading = () => <div class="size-full" />;
35
46
 
36
47
  const HomeRoute = () => (
37
48
  <Suspense fallback={<Loading />}>
38
49
  <Home />
39
50
  </Suspense>
40
- )
51
+ );
41
52
 
42
53
  const SessionRoute = () => (
43
54
  <SessionProviders>
@@ -45,28 +56,36 @@ const SessionRoute = () => (
45
56
  <Session />
46
57
  </Suspense>
47
58
  </SessionProviders>
48
- )
59
+ );
49
60
 
50
- const SessionIndexRoute = () => <Navigate href="session" />
61
+ const SessionIndexRoute = () => <SessionRoute />;
51
62
 
52
63
  function UiI18nBridge(props: ParentProps) {
53
- const language = useLanguage()
54
- return <I18nProvider value={{ locale: language.locale, t: language.t }}>{props.children}</I18nProvider>
64
+ const language = useLanguage();
65
+ return (
66
+ <I18nProvider value={{ locale: language.locale, t: language.t }}>
67
+ {props.children}
68
+ </I18nProvider>
69
+ );
55
70
  }
56
71
 
57
72
  declare global {
58
73
  interface Window {
59
74
  __OPENCODE__?: {
60
- updaterEnabled?: boolean
61
- deepLinks?: string[]
62
- wsl?: boolean
63
- }
75
+ updaterEnabled?: boolean;
76
+ deepLinks?: string[];
77
+ wsl?: boolean;
78
+ };
64
79
  }
65
80
  }
66
81
 
67
82
  function MarkedProviderWithNativeParser(props: ParentProps) {
68
- const platform = usePlatform()
69
- return <MarkedProvider nativeParser={platform.parseMarkdown}>{props.children}</MarkedProvider>
83
+ const platform = usePlatform();
84
+ return (
85
+ <MarkedProvider nativeParser={platform.parseMarkdown}>
86
+ {props.children}
87
+ </MarkedProvider>
88
+ );
70
89
  }
71
90
 
72
91
  function AppShellProviders(props: ParentProps) {
@@ -86,7 +105,7 @@ function AppShellProviders(props: ParentProps) {
86
105
  </LayoutProvider>
87
106
  </PermissionProvider>
88
107
  </SettingsProvider>
89
- )
108
+ );
90
109
  }
91
110
 
92
111
  function SessionProviders(props: ParentProps) {
@@ -98,7 +117,7 @@ function SessionProviders(props: ParentProps) {
98
117
  </PromptProvider>
99
118
  </FileProvider>
100
119
  </TerminalProvider>
101
- )
120
+ );
102
121
  }
103
122
 
104
123
  function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
@@ -107,7 +126,7 @@ function RouterRoot(props: ParentProps<{ appChildren?: JSX.Element }>) {
107
126
  {props.appChildren}
108
127
  {props.children}
109
128
  </AppShellProviders>
110
- )
129
+ );
111
130
  }
112
131
 
113
132
  export function AppBaseProviders(props: ParentProps) {
@@ -120,7 +139,9 @@ export function AppBaseProviders(props: ParentProps) {
120
139
  <ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
121
140
  <DialogProvider>
122
141
  <MarkedProviderWithNativeParser>
123
- <FileComponentProvider component={File}>{props.children}</FileComponentProvider>
142
+ <FileComponentProvider component={File}>
143
+ {props.children}
144
+ </FileComponentProvider>
124
145
  </MarkedProviderWithNativeParser>
125
146
  </DialogProvider>
126
147
  </ErrorBoundary>
@@ -128,22 +149,22 @@ export function AppBaseProviders(props: ParentProps) {
128
149
  </LanguageProvider>
129
150
  </ThemeProvider>
130
151
  </MetaProvider>
131
- )
152
+ );
132
153
  }
133
154
 
134
155
  function ServerKey(props: ParentProps) {
135
- const server = useServer()
156
+ const server = useServer();
136
157
  return (
137
158
  <Show when={server.key} keyed>
138
159
  {props.children}
139
160
  </Show>
140
- )
161
+ );
141
162
  }
142
163
 
143
164
  export function AppInterface(props: {
144
- children?: JSX.Element
145
- defaultServer: ServerConnection.Key
146
- servers?: Array<ServerConnection.Any>
165
+ children?: JSX.Element;
166
+ defaultServer: ServerConnection.Key;
167
+ servers?: Array<ServerConnection.Any>;
147
168
  }) {
148
169
  return (
149
170
  <ServerProvider defaultServer={props.defaultServer} servers={props.servers}>
@@ -152,17 +173,21 @@ export function AppInterface(props: {
152
173
  <GlobalSyncProvider>
153
174
  <Router
154
175
  base={(import.meta.env.BASE_URL ?? "/").replace(/\/+$/, "")}
155
- root={(routerProps) => <RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot>}
176
+ root={(routerProps) => (
177
+ <RouterRoot appChildren={props.children}>
178
+ {routerProps.children}
179
+ </RouterRoot>
180
+ )}
156
181
  >
157
182
  <Route path="/" component={HomeRoute} />
158
183
  <Route path="/:dir" component={DirectoryLayout}>
159
184
  <Route path="/" component={SessionIndexRoute} />
160
- <Route path="/session/:id?" component={SessionRoute} />
185
+ <Route path="/:id?" component={SessionRoute} />
161
186
  </Route>
162
187
  </Router>
163
188
  </GlobalSyncProvider>
164
189
  </GlobalSDKProvider>
165
190
  </ServerKey>
166
191
  </ServerProvider>
167
- )
192
+ );
168
193
  }
@@ -0,0 +1,70 @@
1
+ import { useDialog } from "ikanban-ui/context/dialog"
2
+ import { Dialog } from "ikanban-ui/dialog"
3
+ import { Keybind } from "ikanban-ui/keybind"
4
+ import { List } from "ikanban-ui/list"
5
+ import { createMemo, onCleanup, Show } from "solid-js"
6
+ import { type CommandOption, formatKeybind, useCommand } from "@/context/command"
7
+ import { useLanguage } from "@/context/language"
8
+
9
+ export function DialogCommandPalette() {
10
+ const command = useCommand()
11
+ const dialog = useDialog()
12
+ const language = useLanguage()
13
+ const state = {
14
+ cleanup: undefined as (() => void) | void,
15
+ committed: false,
16
+ }
17
+
18
+ const items = createMemo(() =>
19
+ command.options.filter((option) => !option.disabled && !option.id.startsWith("suggested.") && option.id !== "file.open"),
20
+ )
21
+
22
+ const handleMove = (item: CommandOption | undefined) => {
23
+ state.cleanup?.()
24
+ state.cleanup = item?.onHighlight?.()
25
+ }
26
+
27
+ onCleanup(() => {
28
+ if (state.committed) return
29
+ state.cleanup?.()
30
+ })
31
+
32
+ return (
33
+ <Dialog class="pt-3 pb-0 !max-h-[480px]" transition>
34
+ <List
35
+ search={{
36
+ placeholder: language.t("palette.search.placeholder"),
37
+ autofocus: true,
38
+ hideIcon: true,
39
+ }}
40
+ emptyMessage={language.t("palette.empty")}
41
+ items={items}
42
+ key={(item) => item.id}
43
+ filterKeys={["title", "description", "category"]}
44
+ groupBy={(item) => item.category ?? language.t("palette.group.commands")}
45
+ onMove={handleMove}
46
+ onSelect={(item) => {
47
+ if (!item) return
48
+ state.committed = true
49
+ state.cleanup = undefined
50
+ dialog.close()
51
+ item.onSelect?.("palette")
52
+ }}
53
+ >
54
+ {(item) => (
55
+ <div class="w-full flex items-center justify-between gap-4">
56
+ <div class="flex items-center gap-2 min-w-0">
57
+ <span class="text-14-regular text-text-strong whitespace-nowrap">{item.title}</span>
58
+ <Show when={item.description}>
59
+ <span class="text-14-regular text-text-weak truncate">{item.description}</span>
60
+ </Show>
61
+ </div>
62
+ <Show when={item.keybind}>
63
+ <Keybind class="rounded-[4px]">{formatKeybind(item.keybind ?? "")}</Keybind>
64
+ </Show>
65
+ </div>
66
+ )}
67
+ </List>
68
+ </Dialog>
69
+ )
70
+ }
@@ -75,7 +75,7 @@ export const DialogFork: Component = () => {
75
75
  return
76
76
  }
77
77
  dialog.close()
78
- navigate(`/${base64Encode(sdk.directory)}/session/${forked.data.id}`)
78
+ navigate(`/${base64Encode(sdk.directory)}/${forked.data.id}`)
79
79
  requestAnimationFrame(() => {
80
80
  prompt.set(restored)
81
81
  })
@@ -365,7 +365,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
365
365
 
366
366
  if (item.type === "session") {
367
367
  if (!item.directory || !item.sessionID) return
368
- navigate(`/${base64Encode(item.directory)}/session/${item.sessionID}`)
368
+ navigate(`/${base64Encode(item.directory)}/${item.sessionID}`)
369
369
  return
370
370
  }
371
371
 
@@ -203,7 +203,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
203
203
  if (session) {
204
204
  if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory)
205
205
  layout.handoff.setTabs(base64Encode(sessionDirectory), session.id)
206
- navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
206
+ navigate(`/${base64Encode(sessionDirectory)}/${session.id}`)
207
207
  }
208
208
  }
209
209
  if (!session) {
@@ -192,6 +192,17 @@ export function Titlebar() {
192
192
  </div>
193
193
  </Show>
194
194
  <div class="flex items-center gap-1 shrink-0">
195
+ <Show when={params.dir}>
196
+ <Tooltip placement="bottom" value={language.t("home.title")} openDelay={2000}>
197
+ <Button
198
+ variant="ghost"
199
+ icon="arrow-left"
200
+ class="titlebar-icon w-8 h-6 p-0 box-border"
201
+ onClick={() => navigate("/")}
202
+ aria-label={language.t("home.title")}
203
+ />
204
+ </Tooltip>
205
+ </Show>
195
206
  <TooltipKeybind
196
207
  class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
197
208
  placement="bottom"
@@ -234,7 +245,7 @@ export function Titlebar() {
234
245
  class="titlebar-icon w-8 h-6 p-0 box-border"
235
246
  onClick={() => {
236
247
  if (!params.dir) return
237
- navigate(`/${params.dir}/session`)
248
+ navigate(`/${params.dir}`)
238
249
  }}
239
250
  aria-label={language.t("command.session.new")}
240
251
  />
@@ -245,7 +245,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
245
245
  session: sessionID,
246
246
  })
247
247
 
248
- const href = `/${base64Encode(directory)}/session/${sessionID}`
248
+ const href = `/${base64Encode(directory)}/${sessionID}`
249
249
  if (settings.notifications.agent()) {
250
250
  void platform.notify(language.t("notification.session.responseReady.title"), session.title ?? sessionID, href)
251
251
  }
@@ -278,7 +278,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
278
278
  const description =
279
279
  session?.title ??
280
280
  (typeof error === "string" ? error : language.t("notification.session.error.fallbackDescription"))
281
- const href = sessionID ? `/${base64Encode(directory)}/session/${sessionID}` : `/${base64Encode(directory)}`
281
+ const href = sessionID ? `/${base64Encode(directory)}/${sessionID}` : `/${base64Encode(directory)}`
282
282
  if (settings.notifications.errors()) {
283
283
  void platform.notify(language.t("notification.session.error.title"), description, href)
284
284
  }