ikanban-web 0.2.6 → 0.2.8

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 (43) hide show
  1. package/README.md +25 -20
  2. package/dist/assets/{ghostty-web-BsS8qmby.js → ghostty-web--axxBQj6.js} +1 -1
  3. package/dist/assets/{home-BVcfU4oW.js → home-BAEIhduF.js} +1 -1
  4. package/dist/assets/{index-B1DFsnCu.css → index-B0TEjAod.css} +1 -1
  5. package/dist/assets/{index-BbYqEnGb.js → index-BR3vRMWX.js} +124 -116
  6. package/dist/assets/session-Bp9iU6SU.js +24 -0
  7. package/dist/index.html +19 -6
  8. package/package.json +1 -1
  9. package/src/components/dialog-session-timeline.tsx +85 -0
  10. package/src/components/prompt-input.tsx +3 -3
  11. package/src/components/session/session-header.tsx +2 -211
  12. package/src/components/settings-general.tsx +18 -93
  13. package/src/context/global-sync/child-store.ts +1 -0
  14. package/src/context/global-sync/event-reducer.test.ts +5 -0
  15. package/src/context/global-sync/event-reducer.ts +263 -183
  16. package/src/context/global-sync/types.ts +3 -0
  17. package/src/context/highlights.tsx +4 -176
  18. package/src/context/server.tsx +24 -4
  19. package/src/context/sync.tsx +237 -2
  20. package/src/i18n/en.ts +12 -1
  21. package/src/i18n/parity.test.ts +4 -22
  22. package/src/i18n/zh.ts +23 -1
  23. package/src/pages/layout.tsx +13 -56
  24. package/src/pages/session/review-tab.tsx +3 -1
  25. package/src/pages/session/session-side-panel.tsx +17 -51
  26. package/src/pages/session/use-session-commands.tsx +91 -154
  27. package/src/pages/session.tsx +87 -16
  28. package/dist/assets/session-CZ-wC_3g.js +0 -24
  29. package/src/i18n/ar.ts +0 -751
  30. package/src/i18n/br.ts +0 -759
  31. package/src/i18n/bs.ts +0 -836
  32. package/src/i18n/da.ts +0 -830
  33. package/src/i18n/de.ts +0 -768
  34. package/src/i18n/es.ts +0 -842
  35. package/src/i18n/fr.ts +0 -766
  36. package/src/i18n/ja.ts +0 -755
  37. package/src/i18n/ko.ts +0 -755
  38. package/src/i18n/no.ts +0 -838
  39. package/src/i18n/pl.ts +0 -757
  40. package/src/i18n/ru.ts +0 -838
  41. package/src/i18n/th.ts +0 -828
  42. package/src/i18n/tr.ts +0 -849
  43. package/src/i18n/zht.ts +0 -821
@@ -360,9 +360,8 @@ export default function Page() {
360
360
  })
361
361
 
362
362
  const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined))
363
- const diffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
364
- const reviewCount = createMemo(() => Math.max(info()?.summary?.files ?? 0, diffs().length))
365
- const hasReview = createMemo(() => reviewCount() > 0)
363
+ const sessionDiffs = createMemo(() => (params.id ? (sync.data.session_diff[params.id] ?? []) : []))
364
+ const projectDiffs = createMemo(() => sync.data.project_diff[sdk.directory] ?? [])
366
365
  const revertMessageID = createMemo(() => info()?.revert?.messageID)
367
366
  const messages = createMemo(() => (params.id ? (sync.data.message[params.id] ?? []) : []))
368
367
  const messagesReady = createMemo(() => {
@@ -414,7 +413,7 @@ export default function Page() {
414
413
  const [store, setStore] = createStore({
415
414
  messageId: undefined as string | undefined,
416
415
  mobileTab: "session" as "session" | "changes",
417
- changes: "session" as "session" | "turn",
416
+ changes: "project" as "session" | "project" | "turn",
418
417
  newSessionWorktree: "main",
419
418
  deferRender: false,
420
419
  })
@@ -431,7 +430,42 @@ export default function Page() {
431
430
  }, sessionKey())
432
431
 
433
432
  const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? [])
434
- const reviewDiffs = createMemo(() => (store.changes === "session" ? diffs() : turnDiffs()))
433
+ const reviewDiffs = createMemo(() => {
434
+ if (store.changes === "project") return projectDiffs()
435
+ if (store.changes === "turn") return turnDiffs()
436
+ return sessionDiffs()
437
+ })
438
+ const reviewDiffFiles = createMemo(() => reviewDiffs().map((diff) => diff.file))
439
+ const reviewKinds = createMemo(() => {
440
+ const merge = (a: "add" | "del" | "mix" | undefined, b: "add" | "del" | "mix") => {
441
+ if (!a) return b
442
+ if (a === b) return a
443
+ return "mix" as const
444
+ }
445
+
446
+ const normalize = (p: string) => p.replaceAll("\\", "/").replace(/\/+$/, "")
447
+
448
+ const out = new Map<string, "add" | "del" | "mix">()
449
+ for (const diff of reviewDiffs()) {
450
+ const file = normalize(diff.file)
451
+ const kind = diff.status === "added" ? "add" : diff.status === "deleted" ? "del" : "mix"
452
+
453
+ out.set(file, kind)
454
+
455
+ const parts = file.split("/")
456
+ for (const [idx] of parts.slice(0, -1).entries()) {
457
+ const dir = parts.slice(0, idx + 1).join("/")
458
+ if (!dir) continue
459
+ out.set(dir, merge(out.get(dir), kind))
460
+ }
461
+ }
462
+ return out
463
+ })
464
+ const reviewCount = createMemo(() => reviewDiffs().length)
465
+ const hasReview = createMemo(() => reviewCount() > 0)
466
+ const hasAnyReview = createMemo(
467
+ () => sessionDiffs().length > 0 || projectDiffs().length > 0 || turnDiffs().length > 0,
468
+ )
435
469
 
436
470
  const newSessionWorktree = createMemo(() => {
437
471
  if (store.newSessionWorktree === "create") return "create"
@@ -471,10 +505,17 @@ export default function Page() {
471
505
  const diffsReady = createMemo(() => {
472
506
  const id = params.id
473
507
  if (!id) return true
508
+ if (store.changes === "turn") return true
474
509
  if (!hasReview()) return true
510
+ if (store.changes === "project") return sync.data.project_diff[sdk.directory] !== undefined
475
511
  return sync.data.session_diff[id] !== undefined
476
512
  })
477
513
  const reviewEmptyKey = createMemo(() => {
514
+ if (store.changes === "project") {
515
+ const project = sync.project
516
+ if (!project || project.vcs) return "session.review.emptyProject"
517
+ return "session.review.noVcsProject"
518
+ }
478
519
  const project = sync.project
479
520
  if (!project || project.vcs) return "session.review.empty"
480
521
  return "session.review.noVcs"
@@ -528,7 +569,7 @@ export default function Page() {
528
569
  sessionKey,
529
570
  () => {
530
571
  setStore("messageId", undefined)
531
- setStore("changes", "session")
572
+ setStore("changes", "project")
532
573
  },
533
574
  { defer: true },
534
575
  ),
@@ -705,16 +746,18 @@ export default function Page() {
705
746
  loadFile: file.load,
706
747
  })
707
748
 
708
- const changesOptions = ["session", "turn"] as const
749
+ const changesOptions = ["project", "session", "turn"] as const
709
750
  const changesOptionsList = [...changesOptions]
710
751
 
711
752
  const changesTitle = () => (
712
753
  <Select
713
754
  options={changesOptionsList}
714
755
  current={store.changes}
715
- label={(option) =>
716
- option === "session" ? language.t("ui.sessionReview.title") : language.t("ui.sessionReview.title.lastTurn")
717
- }
756
+ label={(option) => {
757
+ if (option === "project") return language.t("ui.sessionReview.title.project")
758
+ if (option === "turn") return language.t("ui.sessionReview.title.lastTurn")
759
+ return language.t("ui.sessionReview.title")
760
+ }}
718
761
  onSelect={(option) => option && setStore("changes", option)}
719
762
  variant="ghost"
720
763
  size="small"
@@ -743,6 +786,10 @@ export default function Page() {
743
786
  title={changesTitle()}
744
787
  empty={emptyTurn()}
745
788
  diffs={reviewDiffs}
789
+ onRevealFile={(file) => {
790
+ if (store.changes !== "project") return
791
+ void sync.projectDiff.hydrate(file)
792
+ }}
746
793
  view={view}
747
794
  diffStyle={input.diffStyle}
748
795
  onDiffStyleChange={input.onDiffStyleChange}
@@ -767,6 +814,10 @@ export default function Page() {
767
814
  <SessionReviewTab
768
815
  title={changesTitle()}
769
816
  diffs={reviewDiffs}
817
+ onRevealFile={(file) => {
818
+ if (store.changes !== "project") return
819
+ void sync.projectDiff.hydrate(file)
820
+ }}
770
821
  view={view}
771
822
  diffStyle={input.diffStyle}
772
823
  onDiffStyleChange={input.onDiffStyleChange}
@@ -935,14 +986,14 @@ export default function Page() {
935
986
  const first = openedTabs()[0]
936
987
  if (first) return first
937
988
  if (contextOpen()) return "context"
938
- if (reviewTab() && hasReview()) return "review"
989
+ if (reviewTab() && hasAnyReview()) return "review"
939
990
  return "empty"
940
991
  })
941
992
 
942
993
  createEffect(() => {
943
994
  if (!layout.ready()) return
944
995
  if (tabs().active()) return
945
- if (openedTabs().length === 0 && !contextOpen() && !(reviewTab() && hasReview())) return
996
+ if (openedTabs().length === 0 && !contextOpen() && !(reviewTab() && hasAnyReview())) return
946
997
 
947
998
  const next = activeTab()
948
999
  if (next === "empty") return
@@ -958,7 +1009,7 @@ export default function Page() {
958
1009
 
959
1010
  if (opened) {
960
1011
  const active = tabs().active()
961
- const tab = active === "review" || (!active && hasReview()) ? "changes" : "all"
1012
+ const tab = active === "review" || (!active && hasAnyReview()) ? "changes" : "all"
962
1013
  layout.fileTree.setTab(tab)
963
1014
  }
964
1015
  },
@@ -974,9 +1025,19 @@ export default function Page() {
974
1025
  ? desktopFileTreeOpen() || (desktopReviewOpen() && activeTab() === "review")
975
1026
  : store.mobileTab === "changes"
976
1027
  if (!wants) return
977
- if (sync.data.session_diff[id] !== undefined) return
1028
+ if (store.changes === "project") {
1029
+ if (sync.data.project_diff[sdk.directory] !== undefined) return
1030
+ } else if (store.changes === "session") {
1031
+ if (sync.data.session_diff[id] !== undefined) return
1032
+ } else {
1033
+ return
1034
+ }
978
1035
  if (sync.status === "loading") return
979
1036
 
1037
+ if (store.changes === "project") {
1038
+ void sync.projectDiff.diff()
1039
+ return
1040
+ }
980
1041
  void sync.session.diff(id)
981
1042
  })
982
1043
 
@@ -1165,7 +1226,7 @@ export default function Page() {
1165
1226
  <SessionMobileTabs
1166
1227
  open={!isDesktop() && !!params.id}
1167
1228
  mobileTab={store.mobileTab}
1168
- hasReview={hasReview()}
1229
+ hasReview={hasAnyReview()}
1169
1230
  reviewCount={reviewCount()}
1170
1231
  onSession={() => setStore("mobileTab", "session")}
1171
1232
  onChanges={() => setStore("mobileTab", "changes")}
@@ -1281,7 +1342,17 @@ export default function Page() {
1281
1342
  </Show>
1282
1343
  </div>
1283
1344
 
1284
- <SessionSidePanel reviewPanel={reviewPanel} activeDiff={tree.activeDiff} focusReviewDiff={focusReviewDiff} />
1345
+ <SessionSidePanel
1346
+ reviewPanel={reviewPanel}
1347
+ activeDiff={tree.activeDiff}
1348
+ focusReviewDiff={focusReviewDiff}
1349
+ showReview={hasAnyReview}
1350
+ reviewCount={reviewCount}
1351
+ hasReview={hasReview}
1352
+ diffsReady={diffsReady}
1353
+ diffFiles={reviewDiffFiles}
1354
+ kinds={reviewKinds}
1355
+ />
1285
1356
  </div>
1286
1357
 
1287
1358
  <TerminalPanel />