@neurocode-ai/session-ui 1.18.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 (114) hide show
  1. package/package.json +71 -0
  2. package/src/components/apply-patch-file.test.ts +43 -0
  3. package/src/components/apply-patch-file.ts +78 -0
  4. package/src/components/basic-tool.css +358 -0
  5. package/src/components/basic-tool.stories.tsx +133 -0
  6. package/src/components/basic-tool.tsx +343 -0
  7. package/src/components/dock-prompt.stories.tsx +62 -0
  8. package/src/components/dock-prompt.tsx +23 -0
  9. package/src/components/file-media.tsx +291 -0
  10. package/src/components/file-search.tsx +72 -0
  11. package/src/components/file-ssr.tsx +197 -0
  12. package/src/components/file.css +46 -0
  13. package/src/components/file.tsx +1202 -0
  14. package/src/components/line-comment-annotations.tsx +637 -0
  15. package/src/components/line-comment-styles.ts +292 -0
  16. package/src/components/line-comment.stories.tsx +115 -0
  17. package/src/components/line-comment.tsx +439 -0
  18. package/src/components/markdown-cache.tsx +78 -0
  19. package/src/components/markdown-code-state.test.ts +32 -0
  20. package/src/components/markdown-code-state.ts +22 -0
  21. package/src/components/markdown-inline-code-kind.test.ts +46 -0
  22. package/src/components/markdown-inline-code-kind.ts +1915 -0
  23. package/src/components/markdown-preload.test.ts +18 -0
  24. package/src/components/markdown-shiki.worker.ts +104 -0
  25. package/src/components/markdown-stream.test.ts +194 -0
  26. package/src/components/markdown-stream.ts +110 -0
  27. package/src/components/markdown-worker-protocol.test.ts +81 -0
  28. package/src/components/markdown-worker-protocol.ts +48 -0
  29. package/src/components/markdown-worker-queue.test.ts +49 -0
  30. package/src/components/markdown-worker-queue.ts +64 -0
  31. package/src/components/markdown-worker-transport.test.ts +56 -0
  32. package/src/components/markdown-worker-transport.ts +41 -0
  33. package/src/components/markdown-worker.ts +122 -0
  34. package/src/components/markdown.css +377 -0
  35. package/src/components/markdown.stories.tsx +53 -0
  36. package/src/components/markdown.tsx +678 -0
  37. package/src/components/message-file.test.ts +63 -0
  38. package/src/components/message-file.ts +34 -0
  39. package/src/components/message-nav.css +127 -0
  40. package/src/components/message-nav.stories.tsx +7 -0
  41. package/src/components/message-nav.tsx +102 -0
  42. package/src/components/message-part-text.ts +3 -0
  43. package/src/components/message-part.css +1569 -0
  44. package/src/components/message-part.stories.tsx +7 -0
  45. package/src/components/message-part.test.ts +28 -0
  46. package/src/components/message-part.tsx +2662 -0
  47. package/src/components/session-diff.test.ts +135 -0
  48. package/src/components/session-diff.ts +145 -0
  49. package/src/components/session-retry.tsx +74 -0
  50. package/src/components/session-review.css +247 -0
  51. package/src/components/session-review.stories.tsx +7 -0
  52. package/src/components/session-review.tsx +657 -0
  53. package/src/components/session-turn.css +241 -0
  54. package/src/components/session-turn.stories.tsx +7 -0
  55. package/src/components/session-turn.tsx +543 -0
  56. package/src/components/shell-submessage-motion.stories.tsx +346 -0
  57. package/src/components/shell-submessage.css +23 -0
  58. package/src/components/timeline-playground.stories.tsx +2090 -0
  59. package/src/components/tool-count-label.css +57 -0
  60. package/src/components/tool-count-label.tsx +58 -0
  61. package/src/components/tool-count-summary.css +102 -0
  62. package/src/components/tool-count-summary.stories.tsx +238 -0
  63. package/src/components/tool-count-summary.tsx +52 -0
  64. package/src/components/tool-error-card.css +91 -0
  65. package/src/components/tool-error-card.stories.tsx +92 -0
  66. package/src/components/tool-error-card.tsx +157 -0
  67. package/src/components/tool-status-title.css +89 -0
  68. package/src/components/tool-status-title.tsx +133 -0
  69. package/src/context/data.tsx +64 -0
  70. package/src/context/index.ts +1 -0
  71. package/src/pierre/comment-hover.ts +83 -0
  72. package/src/pierre/commented-lines.ts +91 -0
  73. package/src/pierre/diff-selection.ts +71 -0
  74. package/src/pierre/file-find.ts +485 -0
  75. package/src/pierre/file-runtime.ts +114 -0
  76. package/src/pierre/file-selection.ts +85 -0
  77. package/src/pierre/index.ts +186 -0
  78. package/src/pierre/media.ts +110 -0
  79. package/src/pierre/selection-bridge.ts +132 -0
  80. package/src/pierre/virtualizer.ts +100 -0
  81. package/src/pierre/worker.ts +52 -0
  82. package/src/styles/index.css +14 -0
  83. package/src/v2/components/attachment-card-v2.css +70 -0
  84. package/src/v2/components/attachment-card-v2.tsx +33 -0
  85. package/src/v2/components/basic-tool-v2.css +163 -0
  86. package/src/v2/components/basic-tool-v2.stories.tsx +137 -0
  87. package/src/v2/components/basic-tool-v2.tsx +139 -0
  88. package/src/v2/components/comment-card-v2.tsx +64 -0
  89. package/src/v2/components/line-comment-annotations-v2.tsx +220 -0
  90. package/src/v2/components/prompt-input/attachments.ts +266 -0
  91. package/src/v2/components/prompt-input/index.tsx +706 -0
  92. package/src/v2/components/prompt-input/interaction.ts +482 -0
  93. package/src/v2/components/prompt-input/machine.test.ts +164 -0
  94. package/src/v2/components/prompt-input/machine.ts +261 -0
  95. package/src/v2/components/prompt-input/prompt-input.stories.tsx +221 -0
  96. package/src/v2/components/prompt-input/store.test.ts +116 -0
  97. package/src/v2/components/prompt-input/store.ts +152 -0
  98. package/src/v2/components/prompt-input/types.ts +106 -0
  99. package/src/v2/components/session-file-panel-v2.tsx +43 -0
  100. package/src/v2/components/session-progress-indicator-v2.css +875 -0
  101. package/src/v2/components/session-progress-indicator-v2.stories.tsx +66 -0
  102. package/src/v2/components/session-progress-indicator-v2.tsx +32 -0
  103. package/src/v2/components/session-review-empty-changes-v2.tsx +17 -0
  104. package/src/v2/components/session-review-empty-no-git-v2.tsx +28 -0
  105. package/src/v2/components/session-review-file-preview-v2-virtualize.test.ts +13 -0
  106. package/src/v2/components/session-review-file-preview-v2-virtualize.ts +5 -0
  107. package/src/v2/components/session-review-file-preview-v2.tsx +290 -0
  108. package/src/v2/components/session-review-v2.css +458 -0
  109. package/src/v2/components/session-review-v2.tsx +340 -0
  110. package/src/v2/components/tool-error-card-v2.css +200 -0
  111. package/src/v2/components/tool-error-card-v2.stories.tsx +91 -0
  112. package/src/v2/components/tool-error-card-v2.tsx +166 -0
  113. package/sst-env.d.ts +10 -0
  114. package/tsconfig.json +19 -0
@@ -0,0 +1,135 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { normalize, resolveFileDiff, text } from "./session-diff"
3
+
4
+ describe("session diff", () => {
5
+ test("renders whole-file unified patches as complete diffs", () => {
6
+ const diff = {
7
+ file: "a.ts",
8
+ patch:
9
+ "Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,2 +1,2 @@\n one\n-two\n+three\n",
10
+ additions: 1,
11
+ deletions: 1,
12
+ status: "modified" as const,
13
+ }
14
+ const view = normalize(diff)
15
+
16
+ expect(view.fileDiff.name).toBe("a.ts")
17
+ expect(view.fileDiff.isPartial).toBe(false)
18
+ expect(text(view, "deletions")).toBe("one\ntwo\n")
19
+ expect(text(view, "additions")).toBe("one\nthree\n")
20
+ })
21
+
22
+ test("keeps missing final newlines from unified patches", () => {
23
+ const diff = {
24
+ file: "a.ts",
25
+ patch:
26
+ "Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,2 +1,2 @@\n one\n-two\n\\n+three\n\\n",
27
+ additions: 1,
28
+ deletions: 1,
29
+ status: "modified" as const,
30
+ }
31
+ const view = normalize(diff)
32
+
33
+ expect(text(view, "deletions")).toBe("one\ntwo")
34
+ expect(text(view, "additions")).toBe("one\nthree")
35
+ })
36
+
37
+ test("renders whole-file VCS patches as complete diffs", () => {
38
+ const fileDiff = resolveFileDiff({
39
+ file: "a.ts",
40
+ patch:
41
+ "diff --git a/a.ts b/a.ts\nindex 1a2b3c4..5d6e7f8 100644\n--- a/a.ts\n+++ b/a.ts\n@@ -1,2 +1,2 @@\n one\n-old\n+new\n",
42
+ })
43
+
44
+ expect(fileDiff.isPartial).toBe(false)
45
+ expect(fileDiff.additionLines).toEqual(["one\n", "new\n"])
46
+ })
47
+
48
+ test("keeps ordinary leading tool patches partial", () => {
49
+ const fileDiff = resolveFileDiff({
50
+ file: "a.ts",
51
+ patch:
52
+ "Index: a.ts\n===================================================================\n--- a.ts\n+++ a.ts\n@@ -1,5 +1,5 @@\n-old\n+new\n two\n three\n four\n five\n",
53
+ })
54
+
55
+ expect(fileDiff.isPartial).toBe(true)
56
+ expect(fileDiff.additionLines).toEqual(["new\n", "two\n", "three\n", "four\n", "five\n"])
57
+ })
58
+
59
+ test("keeps separated patch hunks partial without complete file contents", () => {
60
+ const fileDiff = resolveFileDiff({
61
+ file: "project.ts",
62
+ patch:
63
+ 'Index: project.ts\n===================================================================\n--- project.ts\t\n+++ project.ts\t\n@@ -1,3 +1,2 @@\n import { and } from "drizzle-orm"\n-import { sql } from "drizzle-orm"\n import { ProjectTable } from "./project.sql"\n@@ -346,3 +345,3 @@\n import { Database } from "@/storage/db"\n-import { ProjectTable } from "./project.sql"\n+import { ProjectTable } from "../project/project.sql"\n import { SessionTable } from "../session/session.sql"\n',
64
+ })
65
+
66
+ expect(fileDiff.isPartial).toBe(true)
67
+ expect(fileDiff.hunks).toHaveLength(2)
68
+ expect(fileDiff.hunks[1]?.collapsedBefore).toBeGreaterThan(0)
69
+ })
70
+
71
+ test("renders headerless persisted patches", () => {
72
+ const view = normalize({
73
+ file: "a.ts",
74
+ patch: "@@ -1 +1 @@\n-old\n+new\n",
75
+ additions: 1,
76
+ deletions: 1,
77
+ status: "modified" as const,
78
+ })
79
+
80
+ expect(view.fileDiff.name).toBe("a.ts")
81
+ expect(view.fileDiff.isPartial).toBe(true)
82
+ expect(text(view, "deletions")).toBe("old\n")
83
+ expect(text(view, "additions")).toBe("new\n")
84
+ })
85
+
86
+ test("does not share headerless patch metadata between files", () => {
87
+ const patch = "@@ -1 +1 @@\n-old\n+new\n"
88
+
89
+ expect(resolveFileDiff({ file: "a.ts", patch }).name).toBe("a.ts")
90
+ expect(resolveFileDiff({ file: "b.ts", patch }).name).toBe("b.ts")
91
+ })
92
+
93
+ test("keeps capped header-only patches partial", () => {
94
+ const fileDiff = resolveFileDiff({
95
+ file: "a.ts",
96
+ patch:
97
+ "Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n",
98
+ })
99
+
100
+ expect(fileDiff.name).toBe("a.ts")
101
+ expect(fileDiff.isPartial).toBe(true)
102
+ expect(fileDiff.hunks).toEqual([])
103
+ })
104
+
105
+ test("keeps full legacy content as a complete diff", () => {
106
+ const diff = {
107
+ file: "a.ts",
108
+ before: "one\n",
109
+ after: "two\n",
110
+ additions: 1,
111
+ deletions: 1,
112
+ status: "modified" as const,
113
+ }
114
+ const view = normalize(diff)
115
+
116
+ expect(view.fileDiff.isPartial).toBe(false)
117
+ expect(text(view, "deletions")).toBe("one\n")
118
+ expect(text(view, "additions")).toBe("two\n")
119
+ })
120
+
121
+ test("ignores malformed persisted patches", () => {
122
+ const diff = {
123
+ file: "a.ts",
124
+ patch:
125
+ "diff --git a/a.ts b/a.ts\nindex ff4ceb2..65a1de0 100644\n--- a/a.ts\n+++ b/a.ts\n@@ -1,3 +1,3 @@\n keep\n+add\n same\r",
126
+ additions: 1,
127
+ deletions: 1,
128
+ status: "modified" as const,
129
+ }
130
+ const view = normalize(diff)
131
+
132
+ expect(text(view, "deletions")).toBe("")
133
+ expect(text(view, "additions")).toBe("")
134
+ })
135
+ })
@@ -0,0 +1,145 @@
1
+ import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"
2
+ import { parsePatch } from "diff"
3
+ import type { SnapshotFileDiff, VcsFileDiff } from "@neurocode-ai/sdk/v2"
4
+ import type { FileDiffInfo } from "@neurocode-ai/client/promise"
5
+
6
+ type LegacyDiff = {
7
+ file: string
8
+ patch?: string
9
+ before?: string
10
+ after?: string
11
+ additions: number
12
+ deletions: number
13
+ status?: "added" | "deleted" | "modified"
14
+ }
15
+
16
+ type SnapshotDiff = SnapshotFileDiff & { file: string }
17
+ type ReviewDiff = SnapshotDiff | FileDiffInfo | VcsFileDiff | LegacyDiff
18
+ export type DiffSource = Pick<LegacyDiff, "file" | "patch" | "before" | "after">
19
+
20
+ export type ViewDiff = {
21
+ file: string
22
+ additions: number
23
+ deletions: number
24
+ status?: "added" | "deleted" | "modified"
25
+ fileDiff: FileDiffMetadata
26
+ }
27
+
28
+ const diffCacheLimit = 16
29
+ const patchFileDiffCache = new Map<string, FileDiffMetadata>()
30
+
31
+ export function resolveFileDiff(diff: DiffSource) {
32
+ if (typeof diff.patch === "string") return fileDiffFromPatch(diff.file, diff.patch)
33
+ return fileDiffFromContent(
34
+ diff.file,
35
+ typeof diff.before === "string" ? diff.before : "",
36
+ typeof diff.after === "string" ? diff.after : "",
37
+ )
38
+ }
39
+
40
+ export function normalize(diff: ReviewDiff): ViewDiff {
41
+ return {
42
+ file: diff.file,
43
+ additions: diff.additions,
44
+ deletions: diff.deletions,
45
+ status: diff.status,
46
+ fileDiff: resolveFileDiff(diff),
47
+ }
48
+ }
49
+
50
+ export function text(diff: ViewDiff, side: "deletions" | "additions") {
51
+ if (side === "deletions") return diff.fileDiff.deletionLines.join("")
52
+ return diff.fileDiff.additionLines.join("")
53
+ }
54
+
55
+ function fileDiffFromPatch(file: string, patch: string) {
56
+ const key = `${file}\0${patch}`
57
+ const hit = patchFileDiffCache.get(key)
58
+ if (hit) {
59
+ patchFileDiffCache.delete(key)
60
+ patchFileDiffCache.set(key, hit)
61
+ return hit
62
+ }
63
+
64
+ const contents = completePatchContents(patch)
65
+ const input = contents ? undefined : patchInput(file, patch)
66
+ const value = contents
67
+ ? fileDiffFromContent(file, contents.before, contents.after)
68
+ : ((input ? parsePatchFiles(input)[0]?.files[0] : undefined) ?? emptyFileDiff(file))
69
+ patchFileDiffCache.set(key, value)
70
+ while (patchFileDiffCache.size > diffCacheLimit) patchFileDiffCache.delete(patchFileDiffCache.keys().next().value!)
71
+ return value
72
+ }
73
+
74
+ function completePatchContents(patch: string) {
75
+ try {
76
+ const parsed = parsePatch(patch)[0]
77
+ if (!parsed || (!parsed.index && !parsed.oldFileName && !parsed.newFileName)) return
78
+ // Snapshot and VCS producers request full context. Tool patches use jsdiff's shorter default context.
79
+ if (!patch.startsWith("diff --git ") && !/^--- [^\n]*\t\r?\n\+\+\+ [^\n]*\t(?:\r?\n|$)/m.test(patch)) return
80
+ // Full patches collapse into one leading hunk. Separated hunks omit ranges and must stay partial.
81
+ if (parsed.hunks.length !== 1) return
82
+
83
+ const hunk = parsed.hunks[0]
84
+ if (!hunk || hunk.oldStart > 1 || hunk.newStart > 1) return
85
+
86
+ const before: Array<{ text: string; newline: boolean }> = []
87
+ const after: Array<{ text: string; newline: boolean }> = []
88
+ let previous: "-" | "+" | " " | undefined
89
+
90
+ for (const line of hunk.lines) {
91
+ if (line.startsWith("\\")) {
92
+ if (previous === "-" || previous === " ") {
93
+ const value = before.at(-1)
94
+ if (value) value.newline = false
95
+ }
96
+ if (previous === "+" || previous === " ") {
97
+ const value = after.at(-1)
98
+ if (value) value.newline = false
99
+ }
100
+ continue
101
+ }
102
+ if (line.startsWith("-")) {
103
+ before.push({ text: line.slice(1), newline: true })
104
+ previous = "-"
105
+ continue
106
+ }
107
+ if (line.startsWith("+")) {
108
+ after.push({ text: line.slice(1), newline: true })
109
+ previous = "+"
110
+ continue
111
+ }
112
+ if (!line.startsWith(" ")) return
113
+ before.push({ text: line.slice(1), newline: true })
114
+ after.push({ text: line.slice(1), newline: true })
115
+ previous = " "
116
+ }
117
+
118
+ const text = (lines: Array<{ text: string; newline: boolean }>) =>
119
+ lines.map((line) => line.text + (line.newline ? "\n" : "")).join("")
120
+ return { before: text(before), after: text(after) }
121
+ } catch {
122
+ return
123
+ }
124
+ }
125
+
126
+ function patchInput(file: string, patch: string) {
127
+ try {
128
+ const parsed = parsePatch(patch)[0]
129
+ if (!parsed) return
130
+ if (parsed.index || parsed.oldFileName || parsed.newFileName) return patch
131
+ if (!parsed.hunks.length) return
132
+ return `Index: ${file}\n===================================================================\n--- ${file}\t\n+++ ${file}\t\n${patch}`
133
+ } catch {
134
+ return
135
+ }
136
+ }
137
+
138
+ function fileDiffFromContent(file: string, before: string, after: string) {
139
+ if (!before && !after) return emptyFileDiff(file)
140
+ return parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after })
141
+ }
142
+
143
+ function emptyFileDiff(file: string) {
144
+ return parseDiffFromFile({ name: file, contents: "" }, { name: file, contents: "" })
145
+ }
@@ -0,0 +1,74 @@
1
+ import { createEffect, createMemo, createSignal, on, onCleanup, Show } from "solid-js"
2
+ import type { SessionStatus } from "@neurocode-ai/sdk/v2/client"
3
+ import { useI18n } from "@neurocode-ai/ui/context/i18n"
4
+ import { Card } from "@neurocode-ai/ui/card"
5
+ import { Tooltip } from "@neurocode-ai/ui/tooltip"
6
+ import { Spinner } from "@neurocode-ai/ui/spinner"
7
+
8
+ export function SessionRetry(props: { status: SessionStatus; show?: boolean }) {
9
+ const i18n = useI18n()
10
+ const retry = createMemo(() => {
11
+ if (props.status.type !== "retry") return
12
+ return props.status
13
+ })
14
+ const [seconds, setSeconds] = createSignal(0)
15
+ createEffect(
16
+ on(retry, (current) => {
17
+ if (!current) return
18
+ const update = () => {
19
+ const next = retry()?.next
20
+ if (!next) return
21
+ setSeconds(Math.round((next - Date.now()) / 1000))
22
+ }
23
+ update()
24
+ const timer = setInterval(update, 1000)
25
+ onCleanup(() => clearInterval(timer))
26
+ }),
27
+ )
28
+ const message = createMemo(() => {
29
+ const current = retry()
30
+ if (!current) return ""
31
+ if (current.message.includes("exceeded your current quota") && current.message.includes("gemini")) {
32
+ return i18n.t("ui.sessionTurn.retry.geminiHot")
33
+ }
34
+ if (current.message.length > 80) return current.message.slice(0, 80) + "..."
35
+ return current.message
36
+ })
37
+ const truncated = createMemo(() => {
38
+ const current = retry()
39
+ if (!current) return false
40
+ return current.message.length > 80
41
+ })
42
+ const info = createMemo(() => {
43
+ const current = retry()
44
+ if (!current) return ""
45
+ const count = Math.max(0, seconds())
46
+ const delay = count > 0 ? i18n.t("ui.sessionTurn.retry.inSeconds", { seconds: count }) : ""
47
+ const retrying = i18n.t("ui.sessionTurn.retry.retrying")
48
+ const line = [retrying, delay].filter(Boolean).join(" ")
49
+ if (!line) return i18n.t("ui.sessionTurn.retry.attempt", { attempt: current.attempt })
50
+ return i18n.t("ui.sessionTurn.retry.attemptLine", { line, attempt: current.attempt })
51
+ })
52
+
53
+ return (
54
+ <Show when={retry() && (props.show ?? true)}>
55
+ <div data-slot="session-turn-retry">
56
+ <Card variant="error" class="error-card">
57
+ <div class="flex items-start gap-2">
58
+ <Spinner class="size-4 mt-0.5" />
59
+ <div class="min-w-0">
60
+ <Show when={truncated()} fallback={<div data-slot="session-turn-retry-message">{message()}</div>}>
61
+ <Tooltip value={retry()?.message ?? ""} placement="top">
62
+ <div data-slot="session-turn-retry-message" class="cursor-help truncate">
63
+ {message()}
64
+ </div>
65
+ </Tooltip>
66
+ </Show>
67
+ <Show when={info()}>{(line) => <div data-slot="session-turn-retry-info">{line()}</div>}</Show>
68
+ </div>
69
+ </div>
70
+ </Card>
71
+ </div>
72
+ </Show>
73
+ )
74
+ }
@@ -0,0 +1,247 @@
1
+ [data-component="session-review"] {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 0px;
5
+ height: 100%;
6
+
7
+ [data-slot="session-review-scroll"] {
8
+ flex: 1 1 auto;
9
+ min-height: 0;
10
+ }
11
+
12
+ .scroll-view__viewport {
13
+ display: flex;
14
+ flex-direction: column;
15
+ }
16
+
17
+ [data-slot="session-review-container"] {
18
+ flex: 1 1 auto;
19
+ padding-right: 0;
20
+ }
21
+
22
+ [data-slot="session-review-header"] {
23
+ z-index: 120;
24
+ background-color: var(--v2-background-bg-base);
25
+ height: 40px;
26
+ padding-bottom: 8px;
27
+ flex-shrink: 0;
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ align-self: stretch;
32
+ }
33
+
34
+ [data-slot="session-review-title"] {
35
+ font-family: var(--font-family-sans);
36
+ font-size: var(--font-size-large);
37
+ font-weight: var(--font-weight-medium);
38
+ line-height: var(--line-height-large);
39
+ color: var(--text-strong);
40
+ }
41
+
42
+ [data-slot="session-review-actions"] {
43
+ display: flex;
44
+ align-items: center;
45
+ column-gap: 12px;
46
+ }
47
+
48
+ [data-slot="session-review-actions"] [data-component="radio-group"] {
49
+ [data-slot="radio-group-wrapper"],
50
+ [data-slot="radio-group-indicator"],
51
+ [data-slot="radio-group-item-control"] {
52
+ border-radius: 6px;
53
+ }
54
+
55
+ [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
56
+ + [data-slot="radio-group-item-label"]:hover
57
+ [data-slot="radio-group-item-control"] {
58
+ border-radius: 4px;
59
+ }
60
+ }
61
+
62
+ [data-component="sticky-accordion-header"] {
63
+ --sticky-accordion-top: 0px;
64
+ }
65
+
66
+ [data-slot="session-review-accordion-item"][data-selected]
67
+ [data-slot="accordion-header"]
68
+ [data-slot="accordion-trigger"] {
69
+ background-color: var(--surface-base-active);
70
+ }
71
+
72
+ [data-slot="accordion-item"] {
73
+ [data-slot="accordion-content"] {
74
+ display: none;
75
+ }
76
+ &[data-expanded] {
77
+ [data-slot="accordion-content"] {
78
+ display: block;
79
+ }
80
+ }
81
+ }
82
+
83
+ [data-slot="accordion-content"] {
84
+ -webkit-user-select: text;
85
+ user-select: text;
86
+ }
87
+
88
+ [data-slot="session-review-accordion-content"] {
89
+ position: relative;
90
+ z-index: 0;
91
+ overflow: hidden;
92
+ }
93
+
94
+ [data-slot="session-review-trigger-content"] {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: space-between;
98
+ width: 100%;
99
+ gap: 20px;
100
+ }
101
+
102
+ [data-slot="session-review-file-info"] {
103
+ flex-grow: 1;
104
+ display: flex;
105
+ align-items: center;
106
+ gap: 12px;
107
+ min-width: 0;
108
+ }
109
+
110
+ [data-slot="session-review-file-name-container"] {
111
+ display: flex;
112
+ align-items: center;
113
+ flex-grow: 1;
114
+ min-width: 0;
115
+ }
116
+
117
+ [data-slot="session-review-directory"] {
118
+ color: var(--text-base);
119
+ text-overflow: ellipsis;
120
+ overflow: hidden;
121
+ white-space: nowrap;
122
+ direction: rtl;
123
+ text-align: left;
124
+ }
125
+
126
+ [data-slot="session-review-filename"] {
127
+ color: var(--text-strong);
128
+ min-width: 0;
129
+ overflow: hidden;
130
+ text-overflow: ellipsis;
131
+ white-space: nowrap;
132
+ }
133
+
134
+ [data-slot="session-review-view-button"] {
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ padding: 2px;
139
+ margin-left: 8px;
140
+ border: none;
141
+ background: transparent;
142
+ color: var(--text-base);
143
+ cursor: pointer;
144
+ border-radius: 4px;
145
+ opacity: 0;
146
+ will-change: opacity;
147
+ transform: translateZ(0);
148
+ transition: opacity 0.15s ease;
149
+
150
+ &:hover {
151
+ color: var(--text-strong);
152
+ background: var(--surface-base);
153
+ }
154
+ }
155
+
156
+ [data-slot="accordion-trigger"]:hover [data-slot="session-review-view-button"] {
157
+ opacity: 1;
158
+ }
159
+
160
+ [data-slot="session-review-trigger-actions"] {
161
+ flex-shrink: 0;
162
+ display: flex;
163
+ gap: 16px;
164
+ align-items: center;
165
+ justify-content: flex-end;
166
+ }
167
+
168
+ [data-slot="session-review-diff-chevron"] {
169
+ display: inline-flex;
170
+ color: var(--icon-weaker);
171
+ transform: rotate(-90deg);
172
+ transition: transform 0.15s ease;
173
+ }
174
+
175
+ [data-slot="accordion-item"][data-expanded] [data-slot="session-review-diff-chevron"] {
176
+ transform: rotate(0deg);
177
+ }
178
+
179
+ [data-slot="session-review-change-group"] {
180
+ display: inline-flex;
181
+ align-items: center;
182
+ gap: 12px;
183
+ }
184
+
185
+ [data-slot="session-review-change"] {
186
+ font-family: var(--font-family-sans);
187
+ font-size: var(--font-size-small);
188
+ font-weight: var(--font-weight-medium);
189
+ }
190
+
191
+ [data-slot="session-review-change"][data-type="added"] {
192
+ color: var(--icon-diff-add-base);
193
+ }
194
+
195
+ [data-slot="session-review-change"][data-type="removed"] {
196
+ color: var(--icon-diff-delete-base);
197
+ }
198
+
199
+ [data-slot="session-review-change"][data-type="modified"] {
200
+ color: var(--icon-diff-modified-base);
201
+ }
202
+
203
+ [data-slot="session-review-diff-wrapper"] {
204
+ position: relative;
205
+ overflow: hidden;
206
+ z-index: 0;
207
+ --line-comment-z: 5;
208
+ --line-comment-popover-z: 30;
209
+ --line-comment-open-z: 6;
210
+ }
211
+
212
+ [data-slot="session-review-large-diff"] {
213
+ padding: 12px;
214
+ background: var(--v2-background-bg-base);
215
+ }
216
+
217
+ [data-slot="session-review-large-diff-title"] {
218
+ font-family: var(--font-family-sans);
219
+ font-size: var(--font-size-small);
220
+ font-weight: var(--font-weight-medium);
221
+ color: var(--text-strong);
222
+ margin-bottom: 4px;
223
+ }
224
+
225
+ [data-slot="session-review-large-diff-meta"] {
226
+ font-family: var(--font-family-sans);
227
+ font-size: var(--font-size-small);
228
+ color: var(--text-weak);
229
+ word-break: break-word;
230
+ }
231
+
232
+ [data-slot="session-review-large-diff-actions"] {
233
+ display: flex;
234
+ gap: 8px;
235
+ margin-top: 10px;
236
+ }
237
+ }
238
+
239
+ body:not([data-new-layout]) [data-component="session-review"] {
240
+ [data-slot="session-review-header"] {
241
+ background-color: var(--background-stronger);
242
+ }
243
+
244
+ [data-slot="session-review-large-diff"] {
245
+ background: var(--background-stronger);
246
+ }
247
+ }
@@ -0,0 +1,7 @@
1
+ // @ts-nocheck
2
+ import * as mod from "./session-review"
3
+ import { create } from "@neurocode-ai/ui/storybook/scaffold"
4
+
5
+ const story = create({ title: "UI/SessionReview", mod })
6
+ export default { title: "UI/SessionReview", id: "components-session-review", component: story.meta.component }
7
+ export const Basic = story.Basic