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.
@@ -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"
@@ -62,7 +65,6 @@ import {
62
65
  effectiveWorkspaceOrder,
63
66
  errorMessage,
64
67
  getDraggableId,
65
- latestRootSession,
66
68
  sortedRootSessions,
67
69
  workspaceKey,
68
70
  } from "./layout/helpers"
@@ -122,9 +124,10 @@ export default function Layout(props: ParentProps) {
122
124
  }
123
125
  const colorSchemeLabel = (scheme: ColorScheme) => language.t(colorSchemeKey[scheme])
124
126
  const currentDir = createMemo(() => decode64(params.dir) ?? "")
127
+ const hasDirectoryRoute = createMemo(() => !!params.dir)
125
128
 
126
129
  const [state, setState] = createStore({
127
- autoselect: !initialDirectory,
130
+ autoselect: false,
128
131
  busyWorkspaces: {} as Record<string, boolean>,
129
132
  hoverSession: undefined as string | undefined,
130
133
  hoverProject: undefined as string | undefined,
@@ -197,15 +200,7 @@ export default function Layout(props: ParentProps) {
197
200
  setHoverProject(undefined)
198
201
  })
199
202
 
200
- const autoselecting = createMemo(() => {
201
- if (params.dir) return false
202
- if (!state.autoselect) return false
203
- if (!pageReady()) return true
204
- if (!layoutReady()) return true
205
- const list = layout.projects.list()
206
- if (list.length > 0) return true
207
- return !!server.projects.last()
208
- })
203
+ const autoselecting = createMemo(() => false)
209
204
 
210
205
  createEffect(() => {
211
206
  if (!state.autoselect) return
@@ -326,7 +321,7 @@ export default function Layout(props: ParentProps) {
326
321
  e.details.type === "permission.asked"
327
322
  ? language.t("notification.permission.description", { sessionTitle, projectName })
328
323
  : language.t("notification.question.description", { sessionTitle, projectName })
329
- const href = `/${base64Encode(directory)}/session/${props.sessionID}`
324
+ const href = `/${base64Encode(directory)}/${props.sessionID}`
330
325
 
331
326
  const now = Date.now()
332
327
  const lastAlerted = alertedAtBySession.get(sessionKey) ?? 0
@@ -427,34 +422,6 @@ export default function Layout(props: ParentProps) {
427
422
  return projects.find((p) => p.worktree === root)
428
423
  })
429
424
 
430
- createEffect(
431
- on(
432
- () => ({ ready: pageReady(), layoutReady: layoutReady(), dir: params.dir, list: layout.projects.list() }),
433
- (value) => {
434
- if (!value.ready) return
435
- if (!value.layoutReady) return
436
- if (!state.autoselect) return
437
- if (value.dir) return
438
-
439
- const last = server.projects.last()
440
-
441
- if (value.list.length === 0) {
442
- if (!last) return
443
- setState("autoselect", false)
444
- openProject(last, false)
445
- navigateToProject(last)
446
- return
447
- }
448
-
449
- const next = value.list.find((project) => project.worktree === last) ?? value.list[0]
450
- if (!next) return
451
- setState("autoselect", false)
452
- openProject(next.worktree, false)
453
- navigateToProject(next.worktree)
454
- },
455
- ),
456
- )
457
-
458
425
  const workspaceName = (directory: string, projectId?: string, branch?: string) => {
459
426
  const key = workspaceKey(directory)
460
427
  const direct = store.workspaceName[key] ?? store.workspaceName[directory]
@@ -794,9 +761,9 @@ export default function Layout(props: ParentProps) {
794
761
  )
795
762
  if (session.id === params.id) {
796
763
  if (nextSession) {
797
- navigate(`/${params.dir}/session/${nextSession.id}`)
764
+ navigate(`/${params.dir}/${nextSession.id}`)
798
765
  } else {
799
- navigate(`/${params.dir}/session`)
766
+ navigate(`/${params.dir}`)
800
767
  }
801
768
  }
802
769
  }
@@ -810,6 +777,15 @@ export default function Layout(props: ParentProps) {
810
777
  keybind: "mod+b",
811
778
  onSelect: () => layout.sidebar.toggle(),
812
779
  },
780
+ {
781
+ id: "file.open",
782
+ title: language.t("command.file.open"),
783
+ description: params.dir ? language.t("palette.search.placeholder") : language.t("command.project.open"),
784
+ category: language.t("command.category.file"),
785
+ keybind: "mod+p",
786
+ slash: "open",
787
+ onSelect: openGlobalFilePicker,
788
+ },
813
789
  {
814
790
  id: "project.open",
815
791
  title: language.t("command.project.open"),
@@ -1055,74 +1031,12 @@ export default function Layout(props: ParentProps) {
1055
1031
  if (!directory) return
1056
1032
  const root = projectRoot(directory)
1057
1033
  server.projects.touch(root)
1058
- const project = layout.projects.list().find((item) => item.worktree === root)
1059
- let dirs = project
1060
- ? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
1061
- : [root]
1062
- const canOpen = (value: string | undefined) => {
1063
- if (!value) return false
1064
- return dirs.some((item) => workspaceKey(item) === workspaceKey(value))
1065
- }
1066
- const refreshDirs = async (target?: string) => {
1067
- if (!target || target === root || canOpen(target)) return canOpen(target)
1068
- const listed = await globalSDK.client.worktree
1069
- .list({ directory: root })
1070
- .then((x) => x.data ?? [])
1071
- .catch(() => [] as string[])
1072
- dirs = effectiveWorkspaceOrder(root, [root, ...listed], store.workspaceOrder[root])
1073
- return canOpen(target)
1074
- }
1075
- const openSession = async (target: { directory: string; id: string }) => {
1076
- if (!canOpen(target.directory)) return false
1077
- const resolved = await globalSDK.client.session
1078
- .get({ sessionID: target.id })
1079
- .then((x) => x.data)
1080
- .catch(() => undefined)
1081
- if (!resolved?.directory) return false
1082
- if (!canOpen(resolved.directory)) return false
1083
- setStore("lastProjectSession", root, { directory: resolved.directory, id: resolved.id, at: Date.now() })
1084
- navigateWithSidebarReset(`/${base64Encode(resolved.directory)}/session/${resolved.id}`)
1085
- return true
1086
- }
1087
-
1088
- const projectSession = store.lastProjectSession[root]
1089
- if (projectSession?.id) {
1090
- await refreshDirs(projectSession.directory)
1091
- const opened = await openSession(projectSession)
1092
- if (opened) return
1093
- clearLastProjectSession(root)
1094
- }
1095
-
1096
- const latest = latestRootSession(
1097
- dirs.map((item) => globalSync.child(item, { bootstrap: false })[0]),
1098
- Date.now(),
1099
- )
1100
- if (latest && (await openSession(latest))) {
1101
- return
1102
- }
1103
-
1104
- const fetched = latestRootSession(
1105
- await Promise.all(
1106
- dirs.map(async (item) => ({
1107
- path: { directory: item },
1108
- session: await globalSDK.client.session
1109
- .list({ directory: item })
1110
- .then((x) => x.data ?? [])
1111
- .catch(() => []),
1112
- })),
1113
- ),
1114
- Date.now(),
1115
- )
1116
- if (fetched && (await openSession(fetched))) {
1117
- return
1118
- }
1119
-
1120
- navigateWithSidebarReset(`/${base64Encode(root)}/session`)
1034
+ navigateWithSidebarReset(`/${base64Encode(root)}`)
1121
1035
  }
1122
1036
 
1123
1037
  function navigateToSession(session: Session | undefined) {
1124
1038
  if (!session) return
1125
- navigateWithSidebarReset(`/${base64Encode(session.directory)}/session/${session.id}`)
1039
+ navigateWithSidebarReset(`/${base64Encode(session.directory)}/${session.id}`)
1126
1040
  }
1127
1041
 
1128
1042
  function openProject(directory: string, navigate = true) {
@@ -1187,7 +1101,7 @@ export default function Layout(props: ParentProps) {
1187
1101
  return
1188
1102
  }
1189
1103
 
1190
- navigateWithSidebarReset(`/${base64Encode(next.worktree)}/session`)
1104
+ navigateWithSidebarReset(`/${base64Encode(next.worktree)}`)
1191
1105
  layout.projects.close(directory)
1192
1106
  queueMicrotask(() => {
1193
1107
  void navigateToProject(next.worktree)
@@ -1232,6 +1146,19 @@ export default function Layout(props: ParentProps) {
1232
1146
  }
1233
1147
  }
1234
1148
 
1149
+ function openGlobalFilePicker() {
1150
+ if (!params.dir) {
1151
+ dialog.show(() => <DialogCommandPalette />)
1152
+ return
1153
+ }
1154
+
1155
+ dialog.show(() => (
1156
+ <FileProvider>
1157
+ <DialogSelectFile />
1158
+ </FileProvider>
1159
+ ))
1160
+ }
1161
+
1235
1162
  const deleteWorkspace = async (root: string, directory: string, leaveDeletedWorkspace = false) => {
1236
1163
  if (directory === root) return
1237
1164
 
@@ -1240,7 +1167,7 @@ export default function Layout(props: ParentProps) {
1240
1167
  const deletedKey = workspaceKey(directory)
1241
1168
  const shouldLeave = leaveDeletedWorkspace || (!!params.dir && currentKey === deletedKey)
1242
1169
  if (!leaveDeletedWorkspace && shouldLeave) {
1243
- navigateWithSidebarReset(`/${base64Encode(root)}/session`)
1170
+ navigateWithSidebarReset(`/${base64Encode(root)}`)
1244
1171
  }
1245
1172
 
1246
1173
  setBusy(directory, true)
@@ -1288,7 +1215,7 @@ export default function Layout(props: ParentProps) {
1288
1215
  const valid = dirs.some((item) => workspaceKey(item) === nextKey)
1289
1216
 
1290
1217
  if (params.dir && projectRoot(nextCurrent) === root && !valid) {
1291
- navigateWithSidebarReset(`/${base64Encode(root)}/session`)
1218
+ navigateWithSidebarReset(`/${base64Encode(root)}`)
1292
1219
  }
1293
1220
  }
1294
1221
 
@@ -1357,7 +1284,7 @@ export default function Layout(props: ParentProps) {
1357
1284
  {
1358
1285
  label: language.t("command.session.new"),
1359
1286
  onClick: () => {
1360
- const href = `/${base64Encode(directory)}/session`
1287
+ const href = `/${base64Encode(directory)}`
1361
1288
  navigate(href)
1362
1289
  layout.mobileSidebar.hide()
1363
1290
  },
@@ -1393,7 +1320,7 @@ export default function Layout(props: ParentProps) {
1393
1320
  const handleDelete = () => {
1394
1321
  const leaveDeletedWorkspace = !!params.dir && workspaceKey(currentDir()) === workspaceKey(props.directory)
1395
1322
  if (leaveDeletedWorkspace) {
1396
- navigateWithSidebarReset(`/${base64Encode(props.root)}/session`)
1323
+ navigateWithSidebarReset(`/${base64Encode(props.root)}`)
1397
1324
  }
1398
1325
  dialog.close()
1399
1326
  void deleteWorkspace(props.root, props.directory, leaveDeletedWorkspace)
@@ -1544,6 +1471,10 @@ export default function Layout(props: ParentProps) {
1544
1471
  )
1545
1472
 
1546
1473
  createEffect(() => {
1474
+ if (!hasDirectoryRoute()) {
1475
+ document.documentElement.style.setProperty("--dialog-left-margin", "0px")
1476
+ return
1477
+ }
1547
1478
  const sidebarWidth = layout.sidebar.opened() ? layout.sidebar.width() : 48
1548
1479
  document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`)
1549
1480
  })
@@ -1691,7 +1622,7 @@ export default function Layout(props: ParentProps) {
1691
1622
  })
1692
1623
 
1693
1624
  globalSync.child(created.directory)
1694
- navigateWithSidebarReset(`/${base64Encode(created.directory)}/session`)
1625
+ navigateWithSidebarReset(`/${base64Encode(created.directory)}`)
1695
1626
  }
1696
1627
 
1697
1628
  const workspaceSidebarCtx: WorkspaceSidebarContext = {
@@ -1884,7 +1815,7 @@ export default function Layout(props: ParentProps) {
1884
1815
  size="large"
1885
1816
  icon="plus-small"
1886
1817
  class="w-full"
1887
- onClick={() => navigateWithSidebarReset(`/${base64Encode(p().worktree)}/session`)}
1818
+ onClick={() => navigateWithSidebarReset(`/${base64Encode(p().worktree)}`)}
1888
1819
  >
1889
1820
  {language.t("command.session.new")}
1890
1821
  </Button>
@@ -1986,136 +1917,143 @@ export default function Layout(props: ParentProps) {
1986
1917
 
1987
1918
  return (
1988
1919
  <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">
1989
- <Titlebar />
1920
+ <Show when={hasDirectoryRoute()}>
1921
+ <Titlebar />
1922
+ </Show>
1990
1923
  <div class="flex-1 min-h-0 flex">
1991
- <nav
1992
- aria-label={language.t("sidebar.nav.projectsAndSessions")}
1993
- data-component="sidebar-nav-desktop"
1994
- classList={{
1995
- "hidden xl:block": true,
1996
- "relative shrink-0": true,
1997
- }}
1998
- style={{ width: layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "64px" }}
1999
- ref={(el) => {
2000
- setState("nav", el)
2001
- }}
2002
- onMouseEnter={() => {
2003
- if (navLeave.current === undefined) return
2004
- clearTimeout(navLeave.current)
2005
- navLeave.current = undefined
2006
- }}
2007
- onMouseLeave={() => {
2008
- aim.reset()
2009
- if (!sidebarHovering()) return
2010
-
2011
- if (navLeave.current !== undefined) clearTimeout(navLeave.current)
2012
- navLeave.current = window.setTimeout(() => {
2013
- navLeave.current = undefined
2014
- setHoverProject(undefined)
2015
- setState("hoverSession", undefined)
2016
- }, 300)
2017
- }}
2018
- >
2019
- <div class="@container w-full h-full contain-strict">
2020
- <SidebarContent
2021
- opened={() => layout.sidebar.opened()}
2022
- aimMove={aim.move}
2023
- projects={() => layout.projects.list()}
2024
- renderProject={(project) => (
2025
- <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} />
2026
- )}
2027
- handleDragStart={handleDragStart}
2028
- handleDragEnd={handleDragEnd}
2029
- handleDragOver={handleDragOver}
2030
- openProjectLabel={language.t("command.project.open")}
2031
- openProjectKeybind={() => command.keybind("project.open")}
2032
- onOpenProject={chooseProject}
2033
- renderProjectOverlay={() => (
2034
- <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
2035
- )}
2036
- settingsLabel={() => language.t("sidebar.settings")}
2037
- settingsKeybind={() => command.keybind("settings.open")}
2038
- onOpenSettings={openSettings}
2039
- helpLabel={() => language.t("sidebar.help")}
2040
- onOpenHelp={() => platform.openLink("https://github.com/isomoes/ikanban/issues")}
2041
- renderPanel={() => (
2042
- <Show when={currentProject()} keyed>
2043
- {(project) => <SidebarPanel project={project} />}
2044
- </Show>
2045
- )}
2046
- />
2047
- </div>
2048
- <Show when={!layout.sidebar.opened() ? hoverProjectData()?.worktree : undefined} keyed>
2049
- {(worktree) => (
2050
- <div class="absolute inset-y-0 left-16 z-50 flex" onMouseEnter={aim.reset}>
2051
- <SidebarPanel project={hoverProjectData()} />
1924
+ <Show when={hasDirectoryRoute()}>
1925
+ <>
1926
+ <nav
1927
+ aria-label={language.t("sidebar.nav.projectsAndSessions")}
1928
+ data-component="sidebar-nav-desktop"
1929
+ classList={{
1930
+ "hidden xl:block": true,
1931
+ "relative shrink-0": true,
1932
+ }}
1933
+ style={{ width: layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "64px" }}
1934
+ ref={(el) => {
1935
+ setState("nav", el)
1936
+ }}
1937
+ onMouseEnter={() => {
1938
+ if (navLeave.current === undefined) return
1939
+ clearTimeout(navLeave.current)
1940
+ navLeave.current = undefined
1941
+ }}
1942
+ onMouseLeave={() => {
1943
+ aim.reset()
1944
+ if (!sidebarHovering()) return
1945
+
1946
+ if (navLeave.current !== undefined) clearTimeout(navLeave.current)
1947
+ navLeave.current = window.setTimeout(() => {
1948
+ navLeave.current = undefined
1949
+ setHoverProject(undefined)
1950
+ setState("hoverSession", undefined)
1951
+ }, 300)
1952
+ }}
1953
+ >
1954
+ <div class="@container w-full h-full contain-strict">
1955
+ <SidebarContent
1956
+ opened={() => layout.sidebar.opened()}
1957
+ aimMove={aim.move}
1958
+ projects={() => layout.projects.list()}
1959
+ renderProject={(project) => (
1960
+ <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} />
1961
+ )}
1962
+ handleDragStart={handleDragStart}
1963
+ handleDragEnd={handleDragEnd}
1964
+ handleDragOver={handleDragOver}
1965
+ openProjectLabel={language.t("command.project.open")}
1966
+ openProjectKeybind={() => command.keybind("project.open")}
1967
+ onOpenProject={chooseProject}
1968
+ renderProjectOverlay={() => (
1969
+ <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
1970
+ )}
1971
+ settingsLabel={() => language.t("sidebar.settings")}
1972
+ settingsKeybind={() => command.keybind("settings.open")}
1973
+ onOpenSettings={openSettings}
1974
+ helpLabel={() => language.t("sidebar.help")}
1975
+ onOpenHelp={() => platform.openLink("https://github.com/isomoes/ikanban/issues")}
1976
+ renderPanel={() => (
1977
+ <Show when={currentProject()} keyed>
1978
+ {(project) => <SidebarPanel project={project} />}
1979
+ </Show>
1980
+ )}
1981
+ />
2052
1982
  </div>
2053
- )}
2054
- </Show>
2055
- <Show when={layout.sidebar.opened()}>
2056
- <ResizeHandle
2057
- direction="horizontal"
2058
- size={layout.sidebar.width()}
2059
- min={244}
2060
- max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
2061
- collapseThreshold={244}
2062
- onResize={layout.sidebar.resize}
2063
- onCollapse={layout.sidebar.close}
2064
- />
2065
- </Show>
2066
- </nav>
2067
- <div class="xl:hidden">
2068
- <div
2069
- classList={{
2070
- "fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true,
2071
- "opacity-100 pointer-events-auto": layout.mobileSidebar.opened(),
2072
- "opacity-0 pointer-events-none": !layout.mobileSidebar.opened(),
2073
- }}
2074
- onClick={(e) => {
2075
- if (e.target === e.currentTarget) layout.mobileSidebar.hide()
2076
- }}
2077
- />
2078
- <nav
2079
- aria-label={language.t("sidebar.nav.projectsAndSessions")}
2080
- data-component="sidebar-nav-mobile"
2081
- classList={{
2082
- "@container fixed top-10 bottom-0 left-0 z-50 w-72 bg-background-base transition-transform duration-200 ease-out": true,
2083
- "translate-x-0": layout.mobileSidebar.opened(),
2084
- "-translate-x-full": !layout.mobileSidebar.opened(),
2085
- }}
2086
- onClick={(e) => e.stopPropagation()}
2087
- >
2088
- <SidebarContent
2089
- mobile
2090
- opened={() => layout.sidebar.opened()}
2091
- aimMove={aim.move}
2092
- projects={() => layout.projects.list()}
2093
- renderProject={(project) => (
2094
- <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile />
2095
- )}
2096
- handleDragStart={handleDragStart}
2097
- handleDragEnd={handleDragEnd}
2098
- handleDragOver={handleDragOver}
2099
- openProjectLabel={language.t("command.project.open")}
2100
- openProjectKeybind={() => command.keybind("project.open")}
2101
- onOpenProject={chooseProject}
2102
- renderProjectOverlay={() => (
2103
- <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
2104
- )}
2105
- settingsLabel={() => language.t("sidebar.settings")}
2106
- settingsKeybind={() => command.keybind("settings.open")}
2107
- onOpenSettings={openSettings}
2108
- helpLabel={() => language.t("sidebar.help")}
2109
- onOpenHelp={() => platform.openLink("https://github.com/isomoes/ikanban/issues")}
2110
- renderPanel={() => <SidebarPanel project={currentProject()} mobile />}
2111
- />
2112
- </nav>
2113
- </div>
1983
+ <Show when={!layout.sidebar.opened() ? hoverProjectData()?.worktree : undefined} keyed>
1984
+ {(worktree) => (
1985
+ <div class="absolute inset-y-0 left-16 z-50 flex" onMouseEnter={aim.reset}>
1986
+ <SidebarPanel project={hoverProjectData()} />
1987
+ </div>
1988
+ )}
1989
+ </Show>
1990
+ <Show when={layout.sidebar.opened()}>
1991
+ <ResizeHandle
1992
+ direction="horizontal"
1993
+ size={layout.sidebar.width()}
1994
+ min={244}
1995
+ max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
1996
+ collapseThreshold={244}
1997
+ onResize={layout.sidebar.resize}
1998
+ onCollapse={layout.sidebar.close}
1999
+ />
2000
+ </Show>
2001
+ </nav>
2002
+ <div class="xl:hidden">
2003
+ <div
2004
+ classList={{
2005
+ "fixed inset-x-0 top-10 bottom-0 z-40 transition-opacity duration-200": true,
2006
+ "opacity-100 pointer-events-auto": layout.mobileSidebar.opened(),
2007
+ "opacity-0 pointer-events-none": !layout.mobileSidebar.opened(),
2008
+ }}
2009
+ onClick={(e) => {
2010
+ if (e.target === e.currentTarget) layout.mobileSidebar.hide()
2011
+ }}
2012
+ />
2013
+ <nav
2014
+ aria-label={language.t("sidebar.nav.projectsAndSessions")}
2015
+ data-component="sidebar-nav-mobile"
2016
+ classList={{
2017
+ "@container fixed top-10 bottom-0 left-0 z-50 w-72 bg-background-base transition-transform duration-200 ease-out": true,
2018
+ "translate-x-0": layout.mobileSidebar.opened(),
2019
+ "-translate-x-full": !layout.mobileSidebar.opened(),
2020
+ }}
2021
+ onClick={(e) => e.stopPropagation()}
2022
+ >
2023
+ <SidebarContent
2024
+ mobile
2025
+ opened={() => layout.sidebar.opened()}
2026
+ aimMove={aim.move}
2027
+ projects={() => layout.projects.list()}
2028
+ renderProject={(project) => (
2029
+ <SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile />
2030
+ )}
2031
+ handleDragStart={handleDragStart}
2032
+ handleDragEnd={handleDragEnd}
2033
+ handleDragOver={handleDragOver}
2034
+ openProjectLabel={language.t("command.project.open")}
2035
+ openProjectKeybind={() => command.keybind("project.open")}
2036
+ onOpenProject={chooseProject}
2037
+ renderProjectOverlay={() => (
2038
+ <ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
2039
+ )}
2040
+ settingsLabel={() => language.t("sidebar.settings")}
2041
+ settingsKeybind={() => command.keybind("settings.open")}
2042
+ onOpenSettings={openSettings}
2043
+ helpLabel={() => language.t("sidebar.help")}
2044
+ onOpenHelp={() => platform.openLink("https://github.com/isomoes/ikanban/issues")}
2045
+ renderPanel={() => <SidebarPanel project={currentProject()} mobile />}
2046
+ />
2047
+ </nav>
2048
+ </div>
2049
+ </>
2050
+ </Show>
2114
2051
 
2115
2052
  <main
2116
2053
  classList={{
2117
2054
  "size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
2118
- "xl:border-l xl:rounded-tl-[12px]": !layout.sidebar.opened(),
2055
+ "xl:border-l xl:rounded-tl-[12px]": hasDirectoryRoute() && !layout.sidebar.opened(),
2056
+ "border-t-0": !hasDirectoryRoute(),
2119
2057
  }}
2120
2058
  >
2121
2059
  <Show when={!autoselecting()} fallback={<div class="size-full" />}>
@@ -190,6 +190,7 @@ export function MessageTimeline(props: {
190
190
  setScrollRef: (el: HTMLDivElement | undefined) => void
191
191
  onScheduleScrollState: (el: HTMLDivElement) => void
192
192
  onAutoScrollHandleScroll: () => void
193
+ isUserScrolled: () => boolean
193
194
  onMarkScrollGesture: (target?: EventTarget | null) => void
194
195
  hasScrollGesture: () => boolean
195
196
  isDesktop: boolean
@@ -345,14 +346,14 @@ export function MessageTimeline(props: {
345
346
  const navigateAfterSessionRemoval = (sessionID: string, parentID?: string, nextSessionID?: string) => {
346
347
  if (params.id !== sessionID) return
347
348
  if (parentID) {
348
- navigate(`/${params.dir}/session/${parentID}`)
349
+ navigate(`/${params.dir}/${parentID}`)
349
350
  return
350
351
  }
351
352
  if (nextSessionID) {
352
- navigate(`/${params.dir}/session/${nextSessionID}`)
353
+ navigate(`/${params.dir}/${nextSessionID}`)
353
354
  return
354
355
  }
355
- navigate(`/${params.dir}/session`)
356
+ navigate(`/${params.dir}`)
356
357
  }
357
358
 
358
359
  const archiveSession = async (sessionID: string) => {
@@ -445,7 +446,7 @@ export function MessageTimeline(props: {
445
446
  const navigateParent = () => {
446
447
  const id = parentID()
447
448
  if (!id) return
448
- navigate(`/${params.dir}/session/${id}`)
449
+ navigate(`/${params.dir}/${id}`)
449
450
  }
450
451
 
451
452
  function DialogDeleteSession(props: { sessionID: string }) {
@@ -538,8 +539,8 @@ export function MessageTimeline(props: {
538
539
  onScroll={(e) => {
539
540
  props.onScheduleScrollState(e.currentTarget)
540
541
  props.onTurnBackfillScroll()
541
- if (!props.hasScrollGesture()) return
542
542
  props.onAutoScrollHandleScroll()
543
+ if (!props.hasScrollGesture() && !props.isUserScrolled()) return
543
544
  props.onMarkScrollGesture(e.currentTarget)
544
545
  if (props.isDesktop) props.onScrollSpyScroll()
545
546
  }}
@@ -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
 
@@ -1276,6 +1276,7 @@ export default function Page() {
1276
1276
  setScrollRef={setScrollRef}
1277
1277
  onScheduleScrollState={scheduleScrollState}
1278
1278
  onAutoScrollHandleScroll={autoScroll.handleScroll}
1279
+ isUserScrolled={autoScroll.userScrolled}
1279
1280
  onMarkScrollGesture={markScrollGesture}
1280
1281
  hasScrollGesture={hasScrollGesture}
1281
1282
  isDesktop={isDesktop()}
@@ -1318,7 +1319,7 @@ export default function Page() {
1318
1319
  if (!target) return
1319
1320
  if (target === sdk.directory) return
1320
1321
  layout.projects.open(target)
1321
- navigate(`/${base64Encode(target)}/session`)
1322
+ navigate(`/${base64Encode(target)}`)
1322
1323
  }}
1323
1324
  />
1324
1325
  </Match>
@@ -1 +0,0 @@
1
- import{u as D,a as C,b as S,c as L,d as P,e as z,f as M,g,i as l,h as a,L as A,j as B,k as E,m as w,D as F,B as u,M as y,F as I,l as R,I as G,S as H,n as N,o as T,t as o}from"./index-BlcL7wgr.js";var q=o("<div>"),J=o('<div class="mt-20 w-full flex flex-col gap-4"><div class="flex gap-2 items-center justify-between pl-3"><div class="text-14-medium text-text-strong"></div></div><ul class="flex flex-col gap-2">'),K=o('<div class="mt-30 mx-auto flex flex-col items-center gap-3"><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">'),O=o('<div class="mx-auto mt-55 w-full md:w-auto px-4">'),Q=o('<div class="text-14-regular text-text-weak">');function V(){const m=D(),$=C(),p=S(),x=L(),_=P(),n=z(),s=M(),b=g(()=>m.data.path.home),k=g(()=>m.data.project.slice().sort((t,e)=>(e.time.updated??e.time.created)-(t.time.updated??t.time.created)).slice(0,5)),j=g(()=>{const t=n.healthy();return t===!0?"bg-icon-success-base":t===!1?"bg-icon-critical-base":"bg-border-weak-base"});function d(t){$.projects.open(t),n.projects.touch(t),_(`/${N(t)}`)}async function h(){function t(e){if(Array.isArray(e))for(const r of e)d(r);else e&&d(e)}if(p.openDirectoryPickerDialog&&n.isLocal()){const e=await p.openDirectoryPickerDialog?.({title:s.t("command.project.open"),multiple:!0});t(e)}else x.show(()=>a(T,{multiple:!0,onSelect:t}),()=>t(null))}return(()=>{var t=O();return l(t,a(A,{class:"md:w-xl opacity-12"}),null),l(t,a(u,{size:"large",variant:"ghost",class:"mt-4 mx-auto text-14-regular text-text-weak",onClick:()=>x.show(()=>a(F,{})),get children(){return[(()=>{var e=q();return B(r=>E(e,{"size-2 rounded-full":!0,[j()]:!0},r)),e})(),w(()=>n.name)]}}),null),l(t,a(H,{get children(){return[a(y,{get when(){return m.data.project.length>0},get children(){var e=J(),r=e.firstChild,i=r.firstChild,f=r.nextSibling;return l(i,()=>s.t("home.recentProjects")),l(r,a(u,{icon:"folder-add-left",size:"normal",class:"pl-2 pr-3",onClick:h,get children(){return s.t("command.project.open")}}),null),l(f,a(I,{get each(){return k()},children:c=>a(u,{size:"large",variant:"ghost",class:"text-14-mono text-left justify-between px-3",onClick:()=>d(c.worktree),get children(){return[w(()=>c.worktree.replace(b(),"~")),(()=>{var v=Q();return l(v,()=>R.fromMillis(c.time.updated??c.time.created).toRelative()),v})()]}})})),e}}),a(y,{when:!0,get children(){var e=K(),r=e.firstChild,i=r.firstChild,f=i.nextSibling;return l(e,a(G,{name:"folder-add-left",size:"large"}),r),l(i,()=>s.t("home.empty.title")),l(f,()=>s.t("home.empty.description")),l(e,a(u,{class:"px-3 mt-1",onClick:h,get children(){return s.t("command.project.open")}}),null),e}})]}}),null),t})()}export{V as default};