ikanban-web 0.2.11 → 0.2.13

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-CE2ZqGRp.js"></script>
31
- <link rel="stylesheet" crossorigin href="/ikanban/assets/index-Z4u1EVXA.css">
30
+ <script type="module" crossorigin src="/ikanban/assets/index-BhuJVelU.js"></script>
31
+ <link rel="stylesheet" crossorigin href="/ikanban/assets/index-7dPULZd1.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.11",
3
+ "version": "0.2.13",
4
4
  "description": "iKanban web app",
5
5
  "private": false,
6
6
  "type": "module",
@@ -0,0 +1,16 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { readFileSync } from "node:fs"
3
+ import { join } from "node:path"
4
+
5
+ const dialogSource = readFileSync(join(import.meta.dir, "dialog-select-server.tsx"), "utf8")
6
+ const frameSource = readFileSync(join(import.meta.dir, "server-list-item-frame.tsx"), "utf8")
7
+
8
+ describe("DialogSelectServer structure", () => {
9
+ test("wraps each http server row so menu actions stay outside the list button", () => {
10
+ expect(dialogSource).toContain("itemWrapper={(item, node) =>")
11
+ expect(dialogSource).toContain("<ServerListItemFrame")
12
+ expect(dialogSource).toContain("row={node}")
13
+ expect(dialogSource).toContain("actions={")
14
+ expect(frameSource).toContain('data-slot="server-list-item-actions"')
15
+ })
16
+ })
@@ -11,6 +11,7 @@ import { useNavigate } from "@solidjs/router"
11
11
  import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
12
12
  import { createStore, reconcile } from "solid-js/store"
13
13
  import { ServerHealthIndicator, ServerRow } from "@/components/server/server-row"
14
+ import { ServerListItemFrame } from "@/components/server-list-item-frame"
14
15
  import { useLanguage } from "@/context/language"
15
16
  import { usePlatform } from "@/context/platform"
16
17
  import { normalizeServerUrl, ServerConnection, useServer } from "@/context/server"
@@ -532,6 +533,61 @@ export function DialogSelectServer() {
532
533
  emptyMessage={language.t("dialog.server.empty")}
533
534
  items={sortedItems}
534
535
  key={(x) => x.http.url}
536
+ itemWrapper={(item, node) => (
537
+ <Show
538
+ when={item.type === "http"}
539
+ fallback={node}
540
+ >
541
+ <ServerListItemFrame
542
+ row={node}
543
+ actions={
544
+ <DropdownMenu>
545
+ <DropdownMenu.Trigger
546
+ as={IconButton}
547
+ icon="dot-grid"
548
+ variant="ghost"
549
+ class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
550
+ onClick={(e: MouseEvent) => e.stopPropagation()}
551
+ onPointerDown={(e: PointerEvent) => e.stopPropagation()}
552
+ />
553
+ <DropdownMenu.Portal>
554
+ <DropdownMenu.Content class="mt-1">
555
+ <DropdownMenu.Item
556
+ onSelect={() => {
557
+ if (item.type !== "http") return
558
+ startEdit(item)
559
+ }}
560
+ >
561
+ <DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
562
+ </DropdownMenu.Item>
563
+ <Show when={canDefault() && defaultUrl() !== item.http.url}>
564
+ <DropdownMenu.Item onSelect={() => setDefault(item.http.url)}>
565
+ <DropdownMenu.ItemLabel>
566
+ {language.t("dialog.server.menu.default")}
567
+ </DropdownMenu.ItemLabel>
568
+ </DropdownMenu.Item>
569
+ </Show>
570
+ <Show when={canDefault() && defaultUrl() === item.http.url}>
571
+ <DropdownMenu.Item onSelect={() => setDefault(null)}>
572
+ <DropdownMenu.ItemLabel>
573
+ {language.t("dialog.server.menu.defaultRemove")}
574
+ </DropdownMenu.ItemLabel>
575
+ </DropdownMenu.Item>
576
+ </Show>
577
+ <DropdownMenu.Separator />
578
+ <DropdownMenu.Item
579
+ onSelect={() => handleRemove(ServerConnection.key(item))}
580
+ class="text-text-on-critical-base hover:bg-surface-critical-weak"
581
+ >
582
+ <DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
583
+ </DropdownMenu.Item>
584
+ </DropdownMenu.Content>
585
+ </DropdownMenu.Portal>
586
+ </DropdownMenu>
587
+ }
588
+ />
589
+ </Show>
590
+ )}
535
591
  onSelect={(x) => {
536
592
  if (x) select(x)
537
593
  }}
@@ -559,57 +615,9 @@ export function DialogSelectServer() {
559
615
  }
560
616
  showCredentials
561
617
  />
562
- <div class="flex items-center justify-center gap-4 pl-4">
563
- <Show when={ServerConnection.key(current()) === key}>
564
- <Icon name="check" class="h-6" />
565
- </Show>
566
-
567
- <Show when={i.type === "http"}>
568
- <DropdownMenu>
569
- <DropdownMenu.Trigger
570
- as={IconButton}
571
- icon="dot-grid"
572
- variant="ghost"
573
- class="shrink-0 size-8 hover:bg-surface-base-hover data-[expanded]:bg-surface-base-active"
574
- onClick={(e: MouseEvent) => e.stopPropagation()}
575
- onPointerDown={(e: PointerEvent) => e.stopPropagation()}
576
- />
577
- <DropdownMenu.Portal>
578
- <DropdownMenu.Content class="mt-1">
579
- <DropdownMenu.Item
580
- onSelect={() => {
581
- if (i.type !== "http") return
582
- startEdit(i)
583
- }}
584
- >
585
- <DropdownMenu.ItemLabel>{language.t("dialog.server.menu.edit")}</DropdownMenu.ItemLabel>
586
- </DropdownMenu.Item>
587
- <Show when={canDefault() && defaultUrl() !== i.http.url}>
588
- <DropdownMenu.Item onSelect={() => setDefault(i.http.url)}>
589
- <DropdownMenu.ItemLabel>
590
- {language.t("dialog.server.menu.default")}
591
- </DropdownMenu.ItemLabel>
592
- </DropdownMenu.Item>
593
- </Show>
594
- <Show when={canDefault() && defaultUrl() === i.http.url}>
595
- <DropdownMenu.Item onSelect={() => setDefault(null)}>
596
- <DropdownMenu.ItemLabel>
597
- {language.t("dialog.server.menu.defaultRemove")}
598
- </DropdownMenu.ItemLabel>
599
- </DropdownMenu.Item>
600
- </Show>
601
- <DropdownMenu.Separator />
602
- <DropdownMenu.Item
603
- onSelect={() => handleRemove(ServerConnection.key(i))}
604
- class="text-text-on-critical-base hover:bg-surface-critical-weak"
605
- >
606
- <DropdownMenu.ItemLabel>{language.t("dialog.server.menu.delete")}</DropdownMenu.ItemLabel>
607
- </DropdownMenu.Item>
608
- </DropdownMenu.Content>
609
- </DropdownMenu.Portal>
610
- </DropdownMenu>
611
- </Show>
612
- </div>
618
+ <Show when={ServerConnection.key(current()) === key}>
619
+ <Icon name="check" class="h-6" />
620
+ </Show>
613
621
  </div>
614
622
  )
615
623
  }}
@@ -0,0 +1,15 @@
1
+ /** @jsxImportSource solid-js */
2
+ import { Show, type JSX } from "solid-js"
3
+
4
+ export function ServerListItemFrame(props: { row: JSX.Element; actions?: JSX.Element }) {
5
+ return (
6
+ <div data-slot="server-list-item-frame" class="flex min-w-0 w-full items-center gap-1">
7
+ <div class="min-w-0 flex-1">{props.row}</div>
8
+ <Show when={props.actions}>
9
+ <div data-slot="server-list-item-actions" class="flex shrink-0 items-center justify-center pr-3">
10
+ {props.actions}
11
+ </div>
12
+ </Show>
13
+ </div>
14
+ )
15
+ }
@@ -2,7 +2,6 @@ import { createEffect, createMemo, Show, untrack } from "solid-js"
2
2
  import { createStore } from "solid-js/store"
3
3
  import { useLocation, useNavigate, useParams } from "@solidjs/router"
4
4
  import { IconButton } from "ikanban-ui/icon-button"
5
- import { Icon } from "ikanban-ui/icon"
6
5
  import { Button } from "ikanban-ui/button"
7
6
  import { Tooltip, TooltipKeybind } from "ikanban-ui/tooltip"
8
7
  import { useTheme } from "ikanban-ui/theme"
@@ -47,7 +46,6 @@ export function Titlebar() {
47
46
 
48
47
  const mac = createMemo(() => platform.platform === "desktop" && platform.os === "macos")
49
48
  const windows = createMemo(() => platform.platform === "desktop" && platform.os === "windows")
50
- const web = createMemo(() => platform.platform === "web")
51
49
  const zoom = () => platform.webviewZoom?.() ?? 1
52
50
  const minHeight = () => (mac() ? `${40 / zoom()}px` : undefined)
53
51
 
@@ -203,34 +201,6 @@ export function Titlebar() {
203
201
  />
204
202
  </Tooltip>
205
203
  </Show>
206
- <TooltipKeybind
207
- class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
208
- placement="bottom"
209
- title={language.t("command.sidebar.toggle")}
210
- keybind={command.keybind("sidebar.toggle")}
211
- >
212
- <Button
213
- variant="ghost"
214
- class="group/sidebar-toggle titlebar-icon w-8 h-6 p-0 box-border"
215
- onClick={layout.sidebar.toggle}
216
- aria-label={language.t("command.sidebar.toggle")}
217
- aria-expanded={layout.sidebar.opened()}
218
- >
219
- <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
220
- <Icon
221
- size="small"
222
- name={layout.sidebar.opened() ? "layout-left-partial" : "layout-left"}
223
- class="group-hover/sidebar-toggle:hidden"
224
- />
225
- <Icon size="small" name="layout-left-partial" class="hidden group-hover/sidebar-toggle:inline-block" />
226
- <Icon
227
- size="small"
228
- name={layout.sidebar.opened() ? "layout-left" : "layout-left-partial"}
229
- class="hidden group-active/sidebar-toggle:inline-block"
230
- />
231
- </div>
232
- </Button>
233
- </TooltipKeybind>
234
204
  <div class="hidden xl:flex items-center shrink-0">
235
205
  <Show when={params.dir}>
236
206
  <TooltipKeybind
@@ -0,0 +1,17 @@
1
+ function normalizeBase(base: string) {
2
+ const trimmed = base.replace(/\/+$/, "")
3
+ if (!trimmed) return "/"
4
+ return trimmed.startsWith("/") ? trimmed : `/${trimmed}`
5
+ }
6
+
7
+ function stripBase(pathname: string, base: string) {
8
+ const normalizedBase = normalizeBase(base)
9
+ if (normalizedBase === "/") return pathname || "/"
10
+ if (pathname === normalizedBase) return "/"
11
+ if (pathname.startsWith(`${normalizedBase}/`)) return pathname.slice(normalizedBase.length) || "/"
12
+ return pathname || "/"
13
+ }
14
+
15
+ export function shouldLoadProjectsOnBootstrap(pathname: string, base: string) {
16
+ return stripBase(pathname, base) !== "/"
17
+ }
@@ -39,6 +39,7 @@ export async function bootstrapGlobal(input: {
39
39
  unknownError: string
40
40
  invalidConfigurationError: string
41
41
  formatMoreCount: (count: number) => string
42
+ loadProjects?: boolean
42
43
  setGlobalStore: SetStoreFunction<GlobalStore>
43
44
  }) {
44
45
  const health = await input.globalSDK.global
@@ -66,16 +67,6 @@ export async function bootstrapGlobal(input: {
66
67
  input.setGlobalStore("config", x.data!)
67
68
  }),
68
69
  ),
69
- retry(() =>
70
- input.globalSDK.project.list().then((x) => {
71
- const projects = (x.data ?? [])
72
- .filter((p) => !!p?.id)
73
- .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
74
- .slice()
75
- .sort((a, b) => cmp(a.id, b.id))
76
- input.setGlobalStore("project", projects)
77
- }),
78
- ),
79
70
  retry(() =>
80
71
  input.globalSDK.provider.list().then((x) => {
81
72
  input.setGlobalStore("provider", normalizeProviderList(x.data!))
@@ -88,6 +79,23 @@ export async function bootstrapGlobal(input: {
88
79
  ),
89
80
  ]
90
81
 
82
+ if (input.loadProjects !== false) {
83
+ tasks.splice(
84
+ 2,
85
+ 0,
86
+ retry(() =>
87
+ input.globalSDK.project.list().then((x) => {
88
+ const projects = (x.data ?? [])
89
+ .filter((p) => !!p?.id)
90
+ .filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
91
+ .slice()
92
+ .sort((a, b) => cmp(a.id, b.id))
93
+ input.setGlobalStore("project", projects)
94
+ }),
95
+ ),
96
+ )
97
+ }
98
+
91
99
  const results = await Promise.allSettled(tasks)
92
100
  const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
93
101
  if (errors.length) {
@@ -5,6 +5,8 @@ import {
5
5
  loadRootSessionsWithFallback,
6
6
  pickDirectoriesToEvict,
7
7
  } from "./global-sync"
8
+ import { bootstrapGlobal } from "./global-sync/bootstrap"
9
+ import { shouldLoadProjectsOnBootstrap } from "./global-sync/bootstrap-mode"
8
10
 
9
11
  describe("pickDirectoriesToEvict", () => {
10
12
  test("keeps pinned stores and evicts idle stores", () => {
@@ -124,3 +126,58 @@ describe("canDisposeDirectory", () => {
124
126
  ).toBe(true)
125
127
  })
126
128
  })
129
+
130
+ describe("bootstrapGlobal", () => {
131
+ test("does not call project list when project loading is disabled", async () => {
132
+ let projectListCalls = 0
133
+ const project = {
134
+ list: async () => {
135
+ projectListCalls += 1
136
+ return { data: [] }
137
+ },
138
+ }
139
+
140
+ const updates: Array<[string, unknown]> = []
141
+
142
+ await bootstrapGlobal({
143
+ globalSDK: {
144
+ global: {
145
+ health: async () => ({ data: { healthy: true } }),
146
+ config: { get: async () => ({ data: {} }) },
147
+ },
148
+ path: { get: async () => ({ data: { state: "", config: "", worktree: "", directory: "", home: "" } }) },
149
+ project,
150
+ provider: {
151
+ list: async () => ({ data: { all: [], connected: [], default: {} } }),
152
+ auth: async () => ({ data: {} }),
153
+ },
154
+ } as never,
155
+ connectErrorTitle: "connect",
156
+ connectErrorDescription: "connect desc",
157
+ requestFailedTitle: "request",
158
+ unknownError: "unknown",
159
+ invalidConfigurationError: "invalid",
160
+ formatMoreCount: () => "",
161
+ setGlobalStore: ((key: string, value: unknown) => {
162
+ updates.push([key, value])
163
+ return value
164
+ }) as never,
165
+ loadProjects: false,
166
+ } as never)
167
+
168
+ expect(projectListCalls).toBe(0)
169
+ expect(updates.some(([key]) => key === "ready")).toBe(true)
170
+ })
171
+ })
172
+
173
+ describe("shouldLoadProjectsOnBootstrap", () => {
174
+ test("skips project loading on the home route", () => {
175
+ expect(shouldLoadProjectsOnBootstrap("/", "/")).toBe(false)
176
+ expect(shouldLoadProjectsOnBootstrap("/ikanban", "/ikanban/")).toBe(false)
177
+ })
178
+
179
+ test("loads projects away from the home route", () => {
180
+ expect(shouldLoadProjectsOnBootstrap("/dGVzdA", "/")).toBe(true)
181
+ expect(shouldLoadProjectsOnBootstrap("/ikanban/dGVzdA", "/ikanban/")).toBe(true)
182
+ })
183
+ })
@@ -26,6 +26,7 @@ import { Persist, persisted } from "@/utils/persist"
26
26
  import type { InitError } from "../pages/error"
27
27
  import { useGlobalSDK } from "./global-sdk"
28
28
  import { bootstrapDirectory, bootstrapGlobal } from "./global-sync/bootstrap"
29
+ import { shouldLoadProjectsOnBootstrap } from "./global-sync/bootstrap-mode"
29
30
  import { createChildStoreManager } from "./global-sync/child-store"
30
31
  import { applyDirectoryEvent, applyGlobalEvent } from "./global-sync/event-reducer"
31
32
  import { createRefreshQueue } from "./global-sync/queue"
@@ -334,6 +335,10 @@ function createGlobalSync() {
334
335
  unknownError: language.t("error.chain.unknown"),
335
336
  invalidConfigurationError: language.t("error.server.invalidConfiguration"),
336
337
  formatMoreCount: (count) => language.t("common.moreCountSuffix", { count }),
338
+ loadProjects: shouldLoadProjectsOnBootstrap(
339
+ globalThis.location?.pathname ?? "/",
340
+ import.meta.env.BASE_URL ?? "/",
341
+ ),
337
342
  setGlobalStore: setBootStore,
338
343
  })
339
344
  }
package/src/i18n/en.ts CHANGED
@@ -133,7 +133,7 @@ export const dict = {
133
133
  "Customize which models appear in the model selector.",
134
134
  "dialog.model.manage.provider.toggle": "Toggle all {{provider}} models",
135
135
 
136
- "dialog.model.unpaid.freeModels.title": "Free models provided by OpenCode",
136
+ "dialog.model.unpaid.freeModels.title": "Free models provided by iKanban",
137
137
  "dialog.model.unpaid.addMore.title": "Add more models from popular providers",
138
138
 
139
139
  "dialog.provider.viewAll": "Show more providers",
@@ -146,7 +146,7 @@ export const dict = {
146
146
  "provider.connect.status.waiting": "Waiting for authorization...",
147
147
  "provider.connect.status.failed": "Authorization failed: {{error}}",
148
148
  "provider.connect.apiKey.description":
149
- "Enter your {{provider}} API key to connect your account and use {{provider}} models in OpenCode.",
149
+ "Enter your {{provider}} API key to connect your account and use {{provider}} models in iKanban.",
150
150
  "provider.connect.apiKey.label": "{{provider}} API key",
151
151
  "provider.connect.apiKey.placeholder": "API key",
152
152
  "provider.connect.apiKey.required": "API key is required",
@@ -160,7 +160,7 @@ export const dict = {
160
160
  "provider.connect.oauth.code.visit.prefix": "Visit ",
161
161
  "provider.connect.oauth.code.visit.link": "this link",
162
162
  "provider.connect.oauth.code.visit.suffix":
163
- " to collect your authorization code to connect your account and use {{provider}} models in OpenCode.",
163
+ " to collect your authorization code to connect your account and use {{provider}} models in iKanban.",
164
164
  "provider.connect.oauth.code.label": "{{method}} authorization code",
165
165
  "provider.connect.oauth.code.placeholder": "Authorization code",
166
166
  "provider.connect.oauth.code.required": "Authorization code is required",
@@ -168,7 +168,7 @@ export const dict = {
168
168
  "provider.connect.oauth.auto.visit.prefix": "Visit ",
169
169
  "provider.connect.oauth.auto.visit.link": "this link",
170
170
  "provider.connect.oauth.auto.visit.suffix":
171
- " and enter the code below to connect your account and use {{provider}} models in OpenCode.",
171
+ " and enter the code below to connect your account and use {{provider}} models in iKanban.",
172
172
  "provider.connect.oauth.auto.confirmationCode": "Confirmation code",
173
173
  "provider.connect.toast.connected.title": "{{provider}} connected",
174
174
  "provider.connect.toast.connected.description":
@@ -486,7 +486,7 @@ export const dict = {
486
486
 
487
487
  "toast.update.title": "Update available",
488
488
  "toast.update.description":
489
- "A new version of OpenCode ({{version}}) is now available to install.",
489
+ "A new version of iKanban ({{version}}) is now available to install.",
490
490
  "toast.update.action.installRestart": "Install and restart",
491
491
  "toast.update.action.notYet": "Not yet",
492
492
 
@@ -497,7 +497,7 @@ export const dict = {
497
497
  "error.page.action.checking": "Checking...",
498
498
  "error.page.action.checkUpdates": "Check for updates",
499
499
  "error.page.action.updateTo": "Update to {{version}}",
500
- "error.page.report.prefix": "Please report this error to the OpenCode team",
500
+ "error.page.report.prefix": "Please report this error to the iKanban team",
501
501
  "error.page.report.discord": "on Discord",
502
502
  "error.page.version": "Version: {{version}}",
503
503
 
@@ -550,7 +550,7 @@ export const dict = {
550
550
  "notification.session.error.fallbackDescription": "An error occurred",
551
551
 
552
552
  "home.recentProjects": "Recent projects",
553
- "home.sessionBoard": "Session board",
553
+ "home.sessionBoard": "Ikanban",
554
554
  "home.sessionBoard.description":
555
555
  "Live session activity grouped into Progress and Idle.",
556
556
  "home.sessionBoard.progress": "Progress",
@@ -667,7 +667,7 @@ export const dict = {
667
667
  "sidebar.workspaces.disable": "Disable workspaces",
668
668
  "sidebar.gettingStarted.title": "Getting started",
669
669
  "sidebar.gettingStarted.line1":
670
- "OpenCode includes free models so you can start immediately.",
670
+ "iKanban includes free models so you can start immediately.",
671
671
  "sidebar.gettingStarted.line2":
672
672
  "Connect any provider to use models, inc. Claude, GPT, Gemini etc.",
673
673
  "sidebar.project.recentSessions": "Recent sessions",
@@ -695,12 +695,12 @@ export const dict = {
695
695
 
696
696
  "settings.general.row.language.title": "Language",
697
697
  "settings.general.row.language.description":
698
- "Change the display language for OpenCode",
698
+ "Change the display language for iKanban",
699
699
  "settings.general.row.appearance.title": "Appearance",
700
700
  "settings.general.row.appearance.description":
701
- "Customise how OpenCode looks on your device",
701
+ "Customise how iKanban looks on your device",
702
702
  "settings.general.row.theme.title": "Theme",
703
- "settings.general.row.theme.description": "Customise how OpenCode is themed.",
703
+ "settings.general.row.theme.description": "Customise how iKanban is themed.",
704
704
  "settings.general.row.font.title": "Font",
705
705
  "settings.general.row.font.description":
706
706
  "Customise the mono font used in code blocks",
@@ -731,7 +731,7 @@ export const dict = {
731
731
 
732
732
  "settings.updates.row.startup.title": "Check for updates on startup",
733
733
  "settings.updates.row.startup.description":
734
- "Automatically check for updates when OpenCode launches",
734
+ "Automatically check for updates when iKanban launches",
735
735
  "settings.updates.row.check.title": "Check for updates",
736
736
  "settings.updates.row.check.description":
737
737
  "Manually check for updates and install if available",
@@ -739,7 +739,7 @@ export const dict = {
739
739
  "settings.updates.action.checking": "Checking...",
740
740
  "settings.updates.toast.latest.title": "You're up to date",
741
741
  "settings.updates.toast.latest.description":
742
- "You're running the latest version of OpenCode.",
742
+ "You're running the latest version of iKanban.",
743
743
  "font.option.ibmPlexMono": "IBM Plex Mono",
744
744
  "font.option.cascadiaCode": "Cascadia Code",
745
745
  "font.option.firaCode": "Fira Code",
package/src/i18n/zh.ts CHANGED
@@ -145,7 +145,7 @@ export const dict = {
145
145
  "dialog.model.recent": "最近",
146
146
  "dialog.model.manage.description": "自定义模型选择器中显示的模型。",
147
147
  "dialog.model.manage.provider.toggle": "切换 {{provider}} 的所有模型",
148
- "dialog.model.unpaid.freeModels.title": "OpenCode 提供的免费模型",
148
+ "dialog.model.unpaid.freeModels.title": "iKanban 提供的免费模型",
149
149
  "dialog.model.unpaid.addMore.title": "从热门提供商添加更多模型",
150
150
 
151
151
  "dialog.provider.viewAll": "查看更多提供商",
@@ -158,7 +158,7 @@ export const dict = {
158
158
  "provider.connect.status.waiting": "等待授权...",
159
159
  "provider.connect.status.failed": "授权失败:{{error}}",
160
160
  "provider.connect.apiKey.description":
161
- "输入你的 {{provider}} API 密钥以连接帐户,并在 OpenCode 中使用 {{provider}} 模型。",
161
+ "输入你的 {{provider}} API 密钥以连接帐户,并在 iKanban 中使用 {{provider}} 模型。",
162
162
  "provider.connect.apiKey.label": "{{provider}} API 密钥",
163
163
  "provider.connect.apiKey.placeholder": "API 密钥",
164
164
  "provider.connect.apiKey.required": "API 密钥为必填项",
@@ -172,7 +172,7 @@ export const dict = {
172
172
  "provider.connect.oauth.code.visit.prefix": "访问 ",
173
173
  "provider.connect.oauth.code.visit.link": "此链接",
174
174
  "provider.connect.oauth.code.visit.suffix":
175
- " 获取授权码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。",
175
+ " 获取授权码,以连接你的帐户并在 iKanban 中使用 {{provider}} 模型。",
176
176
  "provider.connect.oauth.code.label": "{{method}} 授权码",
177
177
  "provider.connect.oauth.code.placeholder": "授权码",
178
178
  "provider.connect.oauth.code.required": "授权码为必填项",
@@ -180,7 +180,7 @@ export const dict = {
180
180
  "provider.connect.oauth.auto.visit.prefix": "访问 ",
181
181
  "provider.connect.oauth.auto.visit.link": "此链接",
182
182
  "provider.connect.oauth.auto.visit.suffix":
183
- " 并输入以下代码,以连接你的帐户并在 OpenCode 中使用 {{provider}} 模型。",
183
+ " 并输入以下代码,以连接你的帐户并在 iKanban 中使用 {{provider}} 模型。",
184
184
  "provider.connect.oauth.auto.confirmationCode": "确认码",
185
185
  "provider.connect.toast.connected.title": "{{provider}} 已连接",
186
186
  "provider.connect.toast.connected.description":
@@ -461,7 +461,7 @@ export const dict = {
461
461
  "toast.session.unshare.failed.description": "取消分享会话时发生错误",
462
462
  "toast.session.listFailed.title": "无法加载 {{project}} 的会话",
463
463
  "toast.update.title": "有可用更新",
464
- "toast.update.description": "OpenCode 有新版本 ({{version}}) 可安装。",
464
+ "toast.update.description": "iKanban 有新版本 ({{version}}) 可安装。",
465
465
  "toast.update.action.installRestart": "安装并重启",
466
466
  "toast.update.action.notYet": "稍后",
467
467
 
@@ -472,7 +472,7 @@ export const dict = {
472
472
  "error.page.action.checking": "检查中...",
473
473
  "error.page.action.checkUpdates": "检查更新",
474
474
  "error.page.action.updateTo": "更新到 {{version}}",
475
- "error.page.report.prefix": "请将此错误报告给 OpenCode 团队",
475
+ "error.page.report.prefix": "请将此错误报告给 iKanban 团队",
476
476
  "error.page.report.discord": "在 Discord 上",
477
477
  "error.page.version": "版本:{{version}}",
478
478
  "error.dev.rootNotFound":
@@ -520,7 +520,7 @@ export const dict = {
520
520
  "notification.session.error.fallbackDescription": "发生错误",
521
521
 
522
522
  "home.recentProjects": "最近项目",
523
- "home.sessionBoard": "会话看板",
523
+ "home.sessionBoard": "Ikanban",
524
524
  "home.sessionBoard.description": "按 Progress 和 Idle 分组。",
525
525
  "home.sessionBoard.progress": "Progress",
526
526
  "home.sessionBoard.idle": "Idle",
@@ -624,7 +624,7 @@ export const dict = {
624
624
  "sidebar.workspaces.enable": "启用工作区",
625
625
  "sidebar.workspaces.disable": "禁用工作区",
626
626
  "sidebar.gettingStarted.title": "入门",
627
- "sidebar.gettingStarted.line1": "OpenCode 提供免费模型,你可以立即开始使用。",
627
+ "sidebar.gettingStarted.line1": "iKanban 提供免费模型,你可以立即开始使用。",
628
628
  "sidebar.gettingStarted.line2":
629
629
  "连接任意提供商即可使用更多模型,如 Claude、GPT、Gemini 等。",
630
630
  "sidebar.project.recentSessions": "最近会话",
@@ -652,12 +652,12 @@ export const dict = {
652
652
  "settings.general.section.feed": "动态",
653
653
  "settings.general.section.display": "显示",
654
654
  "settings.general.row.language.title": "语言",
655
- "settings.general.row.language.description": "更改 OpenCode 的显示语言",
655
+ "settings.general.row.language.description": "更改 iKanban 的显示语言",
656
656
  "settings.general.row.appearance.title": "外观",
657
657
  "settings.general.row.appearance.description":
658
- "自定义 OpenCode 在你的设备上的外观",
658
+ "自定义 iKanban 在你的设备上的外观",
659
659
  "settings.general.row.theme.title": "主题",
660
- "settings.general.row.theme.description": "自定义 OpenCode 的主题。",
660
+ "settings.general.row.theme.description": "自定义 iKanban 的主题。",
661
661
  "settings.general.row.font.title": "字体",
662
662
  "settings.general.row.font.description": "自定义代码块使用的等宽字体",
663
663
  "settings.general.row.reasoningSummaries.title": "显示推理摘要",
@@ -682,14 +682,14 @@ export const dict = {
682
682
  "settings.general.action.releaseNotesCurrent": "查看变更",
683
683
 
684
684
  "settings.updates.row.startup.title": "启动时检查更新",
685
- "settings.updates.row.startup.description": "在 OpenCode 启动时自动检查更新",
685
+ "settings.updates.row.startup.description": "在 iKanban 启动时自动检查更新",
686
686
  "settings.updates.row.check.title": "检查更新",
687
687
  "settings.updates.row.check.description": "手动检查更新并在有更新时安装",
688
688
  "settings.updates.action.checkNow": "立即检查",
689
689
  "settings.updates.action.checking": "正在检查...",
690
690
  "settings.updates.toast.latest.title": "已是最新版本",
691
691
  "settings.updates.toast.latest.description":
692
- "你正在使用最新版本的 OpenCode。",
692
+ "你正在使用最新版本的 iKanban。",
693
693
 
694
694
  "font.option.ibmPlexMono": "IBM Plex Mono",
695
695
  "font.option.cascadiaCode": "Cascadia Code",