ikanban-web 0.2.10 → 0.2.12

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.
Files changed (36) hide show
  1. package/dist/assets/{ghostty-web-CkHg_Jfr.js → ghostty-web-CEQVZrGO.js} +1 -1
  2. package/dist/assets/home-BfGWAM3p.js +1 -0
  3. package/dist/assets/{index-DbuHW0gF.css → index-7dPULZd1.css} +1 -1
  4. package/dist/assets/{index-n2BMSLCz.js → index-WJDzUYhg.js} +106 -106
  5. package/dist/assets/session-BDqUCVwy.js +24 -0
  6. package/dist/index.html +2 -2
  7. package/package.json +1 -1
  8. package/src/app.tsx +82 -61
  9. package/src/components/dialog-command-palette.tsx +70 -0
  10. package/src/components/dialog-fork.tsx +1 -1
  11. package/src/components/dialog-select-file.tsx +1 -1
  12. package/src/components/dialog-select-server.test.ts +16 -0
  13. package/src/components/dialog-select-server.tsx +59 -51
  14. package/src/components/prompt-input/submit.ts +1 -1
  15. package/src/components/server-list-item-frame.tsx +15 -0
  16. package/src/components/titlebar.tsx +12 -31
  17. package/src/context/global-sync/bootstrap-mode.ts +17 -0
  18. package/src/context/global-sync/bootstrap.ts +18 -10
  19. package/src/context/global-sync.test.ts +57 -0
  20. package/src/context/global-sync.tsx +5 -0
  21. package/src/context/notification.tsx +2 -2
  22. package/src/i18n/en.ts +13 -13
  23. package/src/i18n/zh.ts +13 -13
  24. package/src/pages/directory-layout.tsx +2 -2
  25. package/src/pages/home.tsx +104 -19
  26. package/src/pages/layout/sidebar-items.tsx +3 -3
  27. package/src/pages/layout/sidebar-project.tsx +1 -3
  28. package/src/pages/layout/sidebar-shell-helpers.ts +1 -1
  29. package/src/pages/layout/sidebar-shell.test.ts +2 -2
  30. package/src/pages/layout/sidebar-workspace.tsx +1 -1
  31. package/src/pages/layout.tsx +89 -179
  32. package/src/pages/session/message-timeline.tsx +4 -4
  33. package/src/pages/session/use-session-commands.tsx +1 -1
  34. package/src/pages/session.tsx +1 -1
  35. package/dist/assets/home-BTCNns6Z.js +0 -1
  36. package/dist/assets/session-S93i-VnN.js +0 -24
@@ -49,7 +49,10 @@ import { useTheme, type ColorScheme } from "ikanban-ui/theme"
49
49
  import { DialogSelectProvider } from "@/components/dialog-select-provider"
50
50
  import { DialogSelectServer } from "@/components/dialog-select-server"
51
51
  import { DialogSettings } from "@/components/dialog-settings"
52
+ import { DialogCommandPalette } from "@/components/dialog-command-palette"
53
+ import { DialogSelectFile } from "@/components/dialog-select-file"
52
54
  import { useCommand, type CommandOption } from "@/context/command"
55
+ import { FileProvider } from "@/context/file"
53
56
  import { ConstrainDragXAxis } from "@/utils/solid-dnd"
54
57
  import { DialogSelectDirectory } from "@/components/dialog-select-directory"
55
58
  import { DialogEditProject } from "@/components/dialog-edit-project"
@@ -121,9 +124,10 @@ export default function Layout(props: ParentProps) {
121
124
  }
122
125
  const colorSchemeLabel = (scheme: ColorScheme) => language.t(colorSchemeKey[scheme])
123
126
  const currentDir = createMemo(() => decode64(params.dir) ?? "")
127
+ const hasDirectoryRoute = createMemo(() => !!params.dir)
124
128
 
125
129
  const [state, setState] = createStore({
126
- autoselect: !initialDirectory,
130
+ autoselect: false,
127
131
  busyWorkspaces: {} as Record<string, boolean>,
128
132
  hoverSession: undefined as string | undefined,
129
133
  hoverProject: undefined as string | undefined,
@@ -196,15 +200,7 @@ export default function Layout(props: ParentProps) {
196
200
  setHoverProject(undefined)
197
201
  })
198
202
 
199
- const autoselecting = createMemo(() => {
200
- if (params.dir) return false
201
- if (!state.autoselect) return false
202
- if (!pageReady()) return true
203
- if (!layoutReady()) return true
204
- const list = layout.projects.list()
205
- if (list.length > 0) return true
206
- return !!server.projects.last()
207
- })
203
+ const autoselecting = createMemo(() => false)
208
204
 
209
205
  createEffect(() => {
210
206
  if (!state.autoselect) return
@@ -325,7 +321,7 @@ export default function Layout(props: ParentProps) {
325
321
  e.details.type === "permission.asked"
326
322
  ? language.t("notification.permission.description", { sessionTitle, projectName })
327
323
  : language.t("notification.question.description", { sessionTitle, projectName })
328
- const href = `/${base64Encode(directory)}/session/${props.sessionID}`
324
+ const href = `/${base64Encode(directory)}/${props.sessionID}`
329
325
 
330
326
  const now = Date.now()
331
327
  const lastAlerted = alertedAtBySession.get(sessionKey) ?? 0
@@ -426,34 +422,6 @@ export default function Layout(props: ParentProps) {
426
422
  return projects.find((p) => p.worktree === root)
427
423
  })
428
424
 
429
- createEffect(
430
- on(
431
- () => ({ ready: pageReady(), layoutReady: layoutReady(), dir: params.dir, list: layout.projects.list() }),
432
- (value) => {
433
- if (!value.ready) return
434
- if (!value.layoutReady) return
435
- if (!state.autoselect) return
436
- if (value.dir) return
437
-
438
- const last = server.projects.last()
439
-
440
- if (value.list.length === 0) {
441
- if (!last) return
442
- setState("autoselect", false)
443
- openProject(last, false)
444
- navigateToProject(last)
445
- return
446
- }
447
-
448
- const next = value.list.find((project) => project.worktree === last) ?? value.list[0]
449
- if (!next) return
450
- setState("autoselect", false)
451
- openProject(next.worktree, false)
452
- navigateToProject(next.worktree)
453
- },
454
- ),
455
- )
456
-
457
425
  const workspaceName = (directory: string, projectId?: string, branch?: string) => {
458
426
  const key = workspaceKey(directory)
459
427
  const direct = store.workspaceName[key] ?? store.workspaceName[directory]
@@ -793,9 +761,9 @@ export default function Layout(props: ParentProps) {
793
761
  )
794
762
  if (session.id === params.id) {
795
763
  if (nextSession) {
796
- navigate(`/${params.dir}/session/${nextSession.id}`)
764
+ navigate(`/${params.dir}/${nextSession.id}`)
797
765
  } else {
798
- navigate(`/${params.dir}/session`)
766
+ navigate(`/${params.dir}`)
799
767
  }
800
768
  }
801
769
  }
@@ -803,11 +771,13 @@ export default function Layout(props: ParentProps) {
803
771
  command.register("layout", () => {
804
772
  const commands: CommandOption[] = [
805
773
  {
806
- id: "sidebar.toggle",
807
- title: language.t("command.sidebar.toggle"),
808
- category: language.t("command.category.view"),
809
- keybind: "mod+b",
810
- onSelect: () => layout.sidebar.toggle(),
774
+ id: "file.open",
775
+ title: language.t("command.file.open"),
776
+ description: params.dir ? language.t("palette.search.placeholder") : language.t("command.project.open"),
777
+ category: language.t("command.category.file"),
778
+ keybind: "mod+p",
779
+ slash: "open",
780
+ onSelect: openGlobalFilePicker,
811
781
  },
812
782
  {
813
783
  id: "project.open",
@@ -1059,7 +1029,7 @@ export default function Layout(props: ParentProps) {
1059
1029
 
1060
1030
  function navigateToSession(session: Session | undefined) {
1061
1031
  if (!session) return
1062
- navigateWithSidebarReset(`/${base64Encode(session.directory)}/session/${session.id}`)
1032
+ navigateWithSidebarReset(`/${base64Encode(session.directory)}/${session.id}`)
1063
1033
  }
1064
1034
 
1065
1035
  function openProject(directory: string, navigate = true) {
@@ -1124,7 +1094,7 @@ export default function Layout(props: ParentProps) {
1124
1094
  return
1125
1095
  }
1126
1096
 
1127
- navigateWithSidebarReset(`/${base64Encode(next.worktree)}/session`)
1097
+ navigateWithSidebarReset(`/${base64Encode(next.worktree)}`)
1128
1098
  layout.projects.close(directory)
1129
1099
  queueMicrotask(() => {
1130
1100
  void navigateToProject(next.worktree)
@@ -1169,6 +1139,19 @@ export default function Layout(props: ParentProps) {
1169
1139
  }
1170
1140
  }
1171
1141
 
1142
+ function openGlobalFilePicker() {
1143
+ if (!params.dir) {
1144
+ dialog.show(() => <DialogCommandPalette />)
1145
+ return
1146
+ }
1147
+
1148
+ dialog.show(() => (
1149
+ <FileProvider>
1150
+ <DialogSelectFile />
1151
+ </FileProvider>
1152
+ ))
1153
+ }
1154
+
1172
1155
  const deleteWorkspace = async (root: string, directory: string, leaveDeletedWorkspace = false) => {
1173
1156
  if (directory === root) return
1174
1157
 
@@ -1177,7 +1160,7 @@ export default function Layout(props: ParentProps) {
1177
1160
  const deletedKey = workspaceKey(directory)
1178
1161
  const shouldLeave = leaveDeletedWorkspace || (!!params.dir && currentKey === deletedKey)
1179
1162
  if (!leaveDeletedWorkspace && shouldLeave) {
1180
- navigateWithSidebarReset(`/${base64Encode(root)}/session`)
1163
+ navigateWithSidebarReset(`/${base64Encode(root)}`)
1181
1164
  }
1182
1165
 
1183
1166
  setBusy(directory, true)
@@ -1225,7 +1208,7 @@ export default function Layout(props: ParentProps) {
1225
1208
  const valid = dirs.some((item) => workspaceKey(item) === nextKey)
1226
1209
 
1227
1210
  if (params.dir && projectRoot(nextCurrent) === root && !valid) {
1228
- navigateWithSidebarReset(`/${base64Encode(root)}/session`)
1211
+ navigateWithSidebarReset(`/${base64Encode(root)}`)
1229
1212
  }
1230
1213
  }
1231
1214
 
@@ -1294,7 +1277,7 @@ export default function Layout(props: ParentProps) {
1294
1277
  {
1295
1278
  label: language.t("command.session.new"),
1296
1279
  onClick: () => {
1297
- const href = `/${base64Encode(directory)}/session`
1280
+ const href = `/${base64Encode(directory)}`
1298
1281
  navigate(href)
1299
1282
  layout.mobileSidebar.hide()
1300
1283
  },
@@ -1330,7 +1313,7 @@ export default function Layout(props: ParentProps) {
1330
1313
  const handleDelete = () => {
1331
1314
  const leaveDeletedWorkspace = !!params.dir && workspaceKey(currentDir()) === workspaceKey(props.directory)
1332
1315
  if (leaveDeletedWorkspace) {
1333
- navigateWithSidebarReset(`/${base64Encode(props.root)}/session`)
1316
+ navigateWithSidebarReset(`/${base64Encode(props.root)}`)
1334
1317
  }
1335
1318
  dialog.close()
1336
1319
  void deleteWorkspace(props.root, props.directory, leaveDeletedWorkspace)
@@ -1481,8 +1464,7 @@ export default function Layout(props: ParentProps) {
1481
1464
  )
1482
1465
 
1483
1466
  createEffect(() => {
1484
- const sidebarWidth = layout.sidebar.opened() ? layout.sidebar.width() : 48
1485
- document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`)
1467
+ document.documentElement.style.setProperty("--dialog-left-margin", "0px")
1486
1468
  })
1487
1469
 
1488
1470
  const loadedSessionDirs = new Set<string>()
@@ -1628,7 +1610,7 @@ export default function Layout(props: ParentProps) {
1628
1610
  })
1629
1611
 
1630
1612
  globalSync.child(created.directory)
1631
- navigateWithSidebarReset(`/${base64Encode(created.directory)}/session`)
1613
+ navigateWithSidebarReset(`/${base64Encode(created.directory)}`)
1632
1614
  }
1633
1615
 
1634
1616
  const workspaceSidebarCtx: WorkspaceSidebarContext = {
@@ -1821,7 +1803,7 @@ export default function Layout(props: ParentProps) {
1821
1803
  size="large"
1822
1804
  icon="plus-small"
1823
1805
  class="w-full"
1824
- onClick={() => navigateWithSidebarReset(`/${base64Encode(p().worktree)}/session`)}
1806
+ onClick={() => navigateWithSidebarReset(`/${base64Encode(p().worktree)}`)}
1825
1807
  >
1826
1808
  {language.t("command.session.new")}
1827
1809
  </Button>
@@ -1923,136 +1905,64 @@ export default function Layout(props: ParentProps) {
1923
1905
 
1924
1906
  return (
1925
1907
  <div class="relative bg-background-base flex-1 min-h-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
1926
- <Titlebar />
1908
+ <Show when={hasDirectoryRoute()}>
1909
+ <Titlebar />
1910
+ </Show>
1927
1911
  <div class="flex-1 min-h-0 flex">
1928
- <nav
1929
- aria-label={language.t("sidebar.nav.projectsAndSessions")}
1930
- data-component="sidebar-nav-desktop"
1931
- classList={{
1932
- "hidden xl:block": true,
1933
- "relative shrink-0": true,
1934
- }}
1935
- style={{ width: layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "64px" }}
1936
- ref={(el) => {
1937
- setState("nav", el)
1938
- }}
1939
- onMouseEnter={() => {
1940
- if (navLeave.current === undefined) return
1941
- clearTimeout(navLeave.current)
1942
- navLeave.current = undefined
1943
- }}
1944
- onMouseLeave={() => {
1945
- aim.reset()
1946
- if (!sidebarHovering()) return
1947
-
1948
- if (navLeave.current !== undefined) clearTimeout(navLeave.current)
1949
- navLeave.current = window.setTimeout(() => {
1950
- navLeave.current = undefined
1951
- setHoverProject(undefined)
1952
- setState("hoverSession", undefined)
1953
- }, 300)
1954
- }}
1955
- >
1956
- <div class="@container w-full h-full contain-strict">
1957
- <SidebarContent
1958
- opened={() => layout.sidebar.opened()}
1959
- aimMove={aim.move}
1960
- projects={() => layout.projects.list()}
1961
- renderProject={(project) => (
1962
- <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} />
1963
- )}
1964
- handleDragStart={handleDragStart}
1965
- handleDragEnd={handleDragEnd}
1966
- handleDragOver={handleDragOver}
1967
- openProjectLabel={language.t("command.project.open")}
1968
- openProjectKeybind={() => command.keybind("project.open")}
1969
- onOpenProject={chooseProject}
1970
- renderProjectOverlay={() => (
1971
- <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
1972
- )}
1973
- settingsLabel={() => language.t("sidebar.settings")}
1974
- settingsKeybind={() => command.keybind("settings.open")}
1975
- onOpenSettings={openSettings}
1976
- helpLabel={() => language.t("sidebar.help")}
1977
- onOpenHelp={() => platform.openLink("https://github.com/isomoes/ikanban/issues")}
1978
- renderPanel={() => (
1979
- <Show when={currentProject()} keyed>
1980
- {(project) => <SidebarPanel project={project} />}
1981
- </Show>
1982
- )}
1912
+ <Show when={hasDirectoryRoute()}>
1913
+ <div class="xl:hidden">
1914
+ <div
1915
+ classList={{
1916
+ "fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true,
1917
+ "opacity-100 pointer-events-auto": layout.mobileSidebar.opened(),
1918
+ "opacity-0 pointer-events-none": !layout.mobileSidebar.opened(),
1919
+ }}
1920
+ onClick={(e) => {
1921
+ if (e.target === e.currentTarget) layout.mobileSidebar.hide()
1922
+ }}
1983
1923
  />
1924
+ <nav
1925
+ aria-label={language.t("sidebar.nav.projectsAndSessions")}
1926
+ data-component="sidebar-nav-mobile"
1927
+ classList={{
1928
+ "@container fixed top-10 bottom-0 left-0 z-50 w-72 bg-background-base transition-transform duration-200 ease-out": true,
1929
+ "translate-x-0": layout.mobileSidebar.opened(),
1930
+ "-translate-x-full": !layout.mobileSidebar.opened(),
1931
+ }}
1932
+ onClick={(e) => e.stopPropagation()}
1933
+ >
1934
+ <SidebarContent
1935
+ mobile
1936
+ opened={() => false}
1937
+ aimMove={aim.move}
1938
+ projects={() => layout.projects.list()}
1939
+ renderProject={(project) => (
1940
+ <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile />
1941
+ )}
1942
+ handleDragStart={handleDragStart}
1943
+ handleDragEnd={handleDragEnd}
1944
+ handleDragOver={handleDragOver}
1945
+ openProjectLabel={language.t("command.project.open")}
1946
+ openProjectKeybind={() => command.keybind("project.open")}
1947
+ onOpenProject={chooseProject}
1948
+ renderProjectOverlay={() => (
1949
+ <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
1950
+ )}
1951
+ settingsLabel={() => language.t("sidebar.settings")}
1952
+ settingsKeybind={() => command.keybind("settings.open")}
1953
+ onOpenSettings={openSettings}
1954
+ helpLabel={() => language.t("sidebar.help")}
1955
+ onOpenHelp={() => platform.openLink("https://github.com/isomoes/ikanban/issues")}
1956
+ renderPanel={() => <SidebarPanel project={currentProject()} mobile />}
1957
+ />
1958
+ </nav>
1984
1959
  </div>
1985
- <Show when={!layout.sidebar.opened() ? hoverProjectData()?.worktree : undefined} keyed>
1986
- {(worktree) => (
1987
- <div class="absolute inset-y-0 left-16 z-50 flex" onMouseEnter={aim.reset}>
1988
- <SidebarPanel project={hoverProjectData()} />
1989
- </div>
1990
- )}
1991
- </Show>
1992
- <Show when={layout.sidebar.opened()}>
1993
- <ResizeHandle
1994
- direction="horizontal"
1995
- size={layout.sidebar.width()}
1996
- min={244}
1997
- max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
1998
- collapseThreshold={244}
1999
- onResize={layout.sidebar.resize}
2000
- onCollapse={layout.sidebar.close}
2001
- />
2002
- </Show>
2003
- </nav>
2004
- <div class="xl:hidden">
2005
- <div
2006
- classList={{
2007
- "fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true,
2008
- "opacity-100 pointer-events-auto": layout.mobileSidebar.opened(),
2009
- "opacity-0 pointer-events-none": !layout.mobileSidebar.opened(),
2010
- }}
2011
- onClick={(e) => {
2012
- if (e.target === e.currentTarget) layout.mobileSidebar.hide()
2013
- }}
2014
- />
2015
- <nav
2016
- aria-label={language.t("sidebar.nav.projectsAndSessions")}
2017
- data-component="sidebar-nav-mobile"
2018
- classList={{
2019
- "@container fixed top-10 bottom-0 left-0 z-50 w-72 bg-background-base transition-transform duration-200 ease-out": true,
2020
- "translate-x-0": layout.mobileSidebar.opened(),
2021
- "-translate-x-full": !layout.mobileSidebar.opened(),
2022
- }}
2023
- onClick={(e) => e.stopPropagation()}
2024
- >
2025
- <SidebarContent
2026
- mobile
2027
- opened={() => layout.sidebar.opened()}
2028
- aimMove={aim.move}
2029
- projects={() => layout.projects.list()}
2030
- renderProject={(project) => (
2031
- <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile />
2032
- )}
2033
- handleDragStart={handleDragStart}
2034
- handleDragEnd={handleDragEnd}
2035
- handleDragOver={handleDragOver}
2036
- openProjectLabel={language.t("command.project.open")}
2037
- openProjectKeybind={() => command.keybind("project.open")}
2038
- onOpenProject={chooseProject}
2039
- renderProjectOverlay={() => (
2040
- <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
2041
- )}
2042
- settingsLabel={() => language.t("sidebar.settings")}
2043
- settingsKeybind={() => command.keybind("settings.open")}
2044
- onOpenSettings={openSettings}
2045
- helpLabel={() => language.t("sidebar.help")}
2046
- onOpenHelp={() => platform.openLink("https://github.com/isomoes/ikanban/issues")}
2047
- renderPanel={() => <SidebarPanel project={currentProject()} mobile />}
2048
- />
2049
- </nav>
2050
- </div>
1960
+ </Show>
2051
1961
 
2052
1962
  <main
2053
1963
  classList={{
2054
1964
  "size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
2055
- "xl:border-l xl:rounded-tl-[12px]": !layout.sidebar.opened(),
1965
+ "border-t-0": !hasDirectoryRoute(),
2056
1966
  }}
2057
1967
  >
2058
1968
  <Show when={!autoselecting()} fallback={<div class="size-full" />}>
@@ -346,14 +346,14 @@ export function MessageTimeline(props: {
346
346
  const navigateAfterSessionRemoval = (sessionID: string, parentID?: string, nextSessionID?: string) => {
347
347
  if (params.id !== sessionID) return
348
348
  if (parentID) {
349
- navigate(`/${params.dir}/session/${parentID}`)
349
+ navigate(`/${params.dir}/${parentID}`)
350
350
  return
351
351
  }
352
352
  if (nextSessionID) {
353
- navigate(`/${params.dir}/session/${nextSessionID}`)
353
+ navigate(`/${params.dir}/${nextSessionID}`)
354
354
  return
355
355
  }
356
- navigate(`/${params.dir}/session`)
356
+ navigate(`/${params.dir}`)
357
357
  }
358
358
 
359
359
  const archiveSession = async (sessionID: string) => {
@@ -446,7 +446,7 @@ export function MessageTimeline(props: {
446
446
  const navigateParent = () => {
447
447
  const id = parentID()
448
448
  if (!id) return
449
- navigate(`/${params.dir}/session/${id}`)
449
+ navigate(`/${params.dir}/${id}`)
450
450
  }
451
451
 
452
452
  function DialogDeleteSession(props: { sessionID: string }) {
@@ -194,7 +194,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
194
194
  title: language.t("command.session.new"),
195
195
  keybind: "mod+shift+s",
196
196
  slash: "new",
197
- onSelect: () => navigate(`/${params.dir}/session`),
197
+ onSelect: () => navigate(`/${params.dir}`),
198
198
  }),
199
199
  ]);
200
200
 
@@ -1319,7 +1319,7 @@ export default function Page() {
1319
1319
  if (!target) return
1320
1320
  if (target === sdk.directory) return
1321
1321
  layout.projects.open(target)
1322
- navigate(`/${base64Encode(target)}/session`)
1322
+ navigate(`/${base64Encode(target)}`)
1323
1323
  }}
1324
1324
  />
1325
1325
  </Match>
@@ -1 +0,0 @@
1
- import{u as K,a as N,b as q,c as H,d as T,e as V,f as J,g as Q,h as P,s as U,i as W,j as t,k as s,L as X,B as z,l as I,m as Y,n as Z,D as ee,M as A,I as L,S as R,o as O,p as te,t as c,F as re,q as se,r as le,v as ie}from"./index-n2BMSLCz.js";var ne=c("<div>"),oe=c('<div class="mt-6 flex w-full flex-col gap-6"><section class="flex flex-col gap-4"><div class="grid gap-3 lg:grid-cols-2">'),ae=c('<div class="mt-6 rounded-2xl border border-dashed border-border-weak bg-background-base/60 px-6 py-10"><div class="mx-auto flex max-w-md flex-col items-center gap-3 text-center"><div class="flex size-12 items-center justify-center rounded-2xl border border-border-weak bg-background-base/80"></div><div class="flex flex-col gap-1 items-center justify-center"><div class="text-14-medium text-text-strong"></div><div class="text-12-regular text-text-weak">'),ce=c('<div class="mx-auto w-full max-w-7xl px-4 py-6 md:px-6 md:py-8"><div class="flex flex-col gap-4 md:flex-row md:items-start md:justify-between"><div class=min-w-0><div class="flex items-center gap-3"><div class="flex size-10 items-center justify-center rounded-2xl border border-border-weak bg-background-base/80"></div><div><div class="text-18-medium text-text-strong"></div><div class="mt-0.5 text-12-regular text-text-weak"></div></div></div></div><div class="flex items-center gap-2 self-start md:justify-end">'),de=c('<div class="rounded-xl border border-dashed border-border-weak px-3 py-8 text-center text-12-regular text-text-weak">'),ue=c('<section class="rounded-2xl border border-border-weak bg-background-base/80 p-3 md:p-4 min-h-72"><div class="flex items-center justify-between gap-3"><div class="flex items-center gap-2 min-w-0"><div></div><div class="text-14-medium text-text-strong"></div></div><div class="text-12-mono text-text-weak"></div></div><div class="mt-3 flex flex-col gap-2">'),me=c('<span class="min-w-0 flex-1 truncate text-12-regular text-text-weak">'),ge=c('<div class="flex shrink-0 items-center gap-2 text-12-regular text-text-weak"><span>'),fe=c('<div class="overflow-hidden rounded-xl border border-border-weak bg-background-base/70 hover:border-border-strong"><div class="flex items-start justify-between gap-3 px-3 pt-3"><div class="min-w-0 flex-1 pt-1"><div class="truncate text-14-medium leading-tight text-text-strong">');function xe(){const l=K(),j=N(),x=q(),h=H(),v=T(),b=V(),g=J(),i=Q(),C=P(()=>l.data.project.slice().sort((e,r)=>(r.time.updated??r.time.created)-(e.time.updated??e.time.created)).slice(0,5)),n=P(()=>{const e=Date.now(),r={progress:[],idle:[]};for(const a of C()){const[d]=l.child(a.worktree),u=U(d,e);for(const f of u){const y=d.session_status[f.id],B=f.time.updated??f.time.created,_={session:f,projectDirectory:a.worktree,updatedAt:B};if(y?.type==="busy"||y?.type==="retry"){r.progress.push(_);continue}r.idle.push(_)}}for(const a of Object.keys(r))r[a].sort((d,u)=>u.updatedAt-d.updatedAt);return r});W(()=>{for(const e of C())l.child(e.worktree)});const w=P(()=>{const e=g.healthy();return e===!0?"bg-icon-success-base":e===!1?"bg-icon-critical-base":"bg-border-weak-base"});function $(e){j.projects.open(e),g.projects.touch(e),v(`/${O(e)}/session`)}function S(e,r){j.projects.open(e),g.projects.touch(e),v(`/${O(e)}/session/${r}`)}async function D(e,r){const[,a]=l.child(e,{bootstrap:!1});await b.client.session.update({sessionID:r,time:{archived:Date.now()}}),a("session",d=>d.filter(u=>u.id!==r))}async function o(){function e(r){if(Array.isArray(r))for(const a of r)$(a);else r&&$(r)}if(x.openDirectoryPickerDialog&&g.isLocal()){const r=await x.openDirectoryPickerDialog?.({title:i.t("command.project.open"),multiple:!0});e(r)}else h.show(()=>s(te,{multiple:!0,onSelect:e}),()=>e(null))}return(()=>{var e=ce(),r=e.firstChild,a=r.firstChild,d=a.firstChild,u=d.firstChild,f=u.nextSibling,y=f.firstChild,B=y.nextSibling,_=a.nextSibling;return t(u,s(X,{class:"w-6 opacity-90"})),t(y,()=>i.t("home.sessionBoard")),t(B,()=>i.t("home.sessionBoard.description")),t(_,s(z,{icon:"folder-add-left",size:"normal",class:"pl-2 pr-3",onClick:o,get children(){return i.t("command.project.open")}}),null),t(_,s(z,{size:"normal",variant:"ghost",class:"pr-3 text-13-regular text-text-weak",onClick:()=>h.show(()=>s(ee,{})),get children(){return[(()=>{var m=ne();return I(p=>Y(m,{"size-2 rounded-full":!0,[w()]:!0},p)),m})(),Z(()=>g.name)]}}),null),t(e,s(R,{get children(){return[s(A,{get when(){return l.data.project.length>0},get children(){var m=oe(),p=m.firstChild,k=p.firstChild;return t(k,s(E,{get title(){return i.t("home.sessionBoard.progress")},icon:"brain",tone:"progress",get cards(){return n().progress},onOpen:S,onArchive:D,get empty(){return i.t("home.sessionBoard.emptyProgress")}}),null),t(k,s(E,{get title(){return i.t("home.sessionBoard.idle")},icon:"dash",tone:"idle",get cards(){return n().idle},onOpen:S,onArchive:D,get empty(){return i.t("home.sessionBoard.emptyIdle")}}),null),m}}),s(A,{when:!0,get children(){var m=ae(),p=m.firstChild,k=p.firstChild,F=k.nextSibling,M=F.firstChild,G=M.nextSibling;return t(k,s(L,{name:"folder-add-left",size:"large"})),t(M,()=>i.t("home.empty.title")),t(G,()=>i.t("home.empty.description")),t(p,s(z,{class:"mt-1 px-3",onClick:o,get children(){return i.t("command.project.open")}}),null),m}})]}}),null),e})()}function E(l){const j=()=>l.tone==="progress"?"bg-emerald-500/12 text-emerald-300 border-emerald-500/20":"bg-sky-500/12 text-sky-300 border-sky-500/20";return(()=>{var x=ue(),h=x.firstChild,v=h.firstChild,b=v.firstChild,g=b.nextSibling,i=v.nextSibling,C=h.nextSibling;return t(b,s(L,{get name(){return l.icon},size:"small"})),t(g,()=>l.title),t(i,()=>l.cards.length),t(C,s(R,{get children(){return[s(A,{get when(){return l.cards.length>0},get children(){return s(re,{get each(){return l.cards},children:n=>(()=>{var w=fe(),$=w.firstChild,S=$.firstChild,D=S.firstChild;return t(D,()=>n.session.title),t($,s(se,{icon:"archive",variant:"ghost",class:"size-7 rounded-md","aria-label":"Archive session",onClick:o=>{o.preventDefault(),o.stopPropagation(),l.onArchive(n.projectDirectory,n.session.id)}}),null),t(w,s(z,{variant:"ghost",class:"flex h-auto w-full min-w-0 items-center justify-between gap-3 rounded-none border-0 bg-transparent px-3 pb-3 pt-2 text-left",onClick:()=>l.onOpen(n.projectDirectory,n.session.id),get children(){return[(()=>{var o=me();return t(o,()=>n.projectDirectory.replace(/^.*?([^/\\]+(?:[/\\][^/\\]+)?)$/,"$1")),o})(),(()=>{var o=ge(),e=o.firstChild;return t(e,()=>le.fromMillis(n.updatedAt).toRelative()),t(o,s(L,{name:"arrow-right",size:"small",class:"text-icon-weak shrink-0"}),null),o})()]}}),null),w})()})}}),s(A,{when:!0,get children(){var n=de();return t(n,()=>l.empty),n}})]}})),I(()=>ie(b,`size-7 rounded-full border flex items-center justify-center ${j()}`)),x})()}export{xe as default};