@neocode-ai/web 1.1.1

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 (86) hide show
  1. package/README.md +54 -0
  2. package/astro.config.mjs +145 -0
  3. package/config.mjs +14 -0
  4. package/package.json +41 -0
  5. package/public/robots.txt +6 -0
  6. package/public/theme.json +183 -0
  7. package/src/assets/lander/check.svg +2 -0
  8. package/src/assets/lander/copy.svg +2 -0
  9. package/src/assets/lander/screenshot-github.png +0 -0
  10. package/src/assets/lander/screenshot-splash.png +0 -0
  11. package/src/assets/lander/screenshot-vscode.png +0 -0
  12. package/src/assets/lander/screenshot.png +0 -0
  13. package/src/assets/logo-dark.svg +20 -0
  14. package/src/assets/logo-light.svg +20 -0
  15. package/src/assets/logo-ornate-dark.svg +18 -0
  16. package/src/assets/logo-ornate-light.svg +18 -0
  17. package/src/assets/web/web-homepage-active-session.png +0 -0
  18. package/src/assets/web/web-homepage-new-session.png +0 -0
  19. package/src/assets/web/web-homepage-see-servers.png +0 -0
  20. package/src/components/Head.astro +50 -0
  21. package/src/components/Header.astro +128 -0
  22. package/src/components/Hero.astro +11 -0
  23. package/src/components/Lander.astro +713 -0
  24. package/src/components/Share.tsx +634 -0
  25. package/src/components/SiteTitle.astro +59 -0
  26. package/src/components/icons/custom.tsx +87 -0
  27. package/src/components/icons/index.tsx +4454 -0
  28. package/src/components/share/common.tsx +77 -0
  29. package/src/components/share/content-bash.module.css +85 -0
  30. package/src/components/share/content-bash.tsx +67 -0
  31. package/src/components/share/content-code.module.css +26 -0
  32. package/src/components/share/content-code.tsx +32 -0
  33. package/src/components/share/content-diff.module.css +153 -0
  34. package/src/components/share/content-diff.tsx +231 -0
  35. package/src/components/share/content-error.module.css +64 -0
  36. package/src/components/share/content-error.tsx +24 -0
  37. package/src/components/share/content-markdown.module.css +154 -0
  38. package/src/components/share/content-markdown.tsx +75 -0
  39. package/src/components/share/content-text.module.css +63 -0
  40. package/src/components/share/content-text.tsx +37 -0
  41. package/src/components/share/copy-button.module.css +30 -0
  42. package/src/components/share/copy-button.tsx +28 -0
  43. package/src/components/share/part.module.css +428 -0
  44. package/src/components/share/part.tsx +780 -0
  45. package/src/components/share.module.css +832 -0
  46. package/src/content/docs/1-0.mdx +67 -0
  47. package/src/content/docs/acp.mdx +156 -0
  48. package/src/content/docs/agents.mdx +720 -0
  49. package/src/content/docs/cli.mdx +597 -0
  50. package/src/content/docs/commands.mdx +323 -0
  51. package/src/content/docs/config.mdx +683 -0
  52. package/src/content/docs/custom-tools.mdx +170 -0
  53. package/src/content/docs/ecosystem.mdx +76 -0
  54. package/src/content/docs/enterprise.mdx +170 -0
  55. package/src/content/docs/formatters.mdx +130 -0
  56. package/src/content/docs/github.mdx +321 -0
  57. package/src/content/docs/gitlab.mdx +195 -0
  58. package/src/content/docs/ide.mdx +48 -0
  59. package/src/content/docs/index.mdx +359 -0
  60. package/src/content/docs/keybinds.mdx +191 -0
  61. package/src/content/docs/lsp.mdx +188 -0
  62. package/src/content/docs/mcp-servers.mdx +511 -0
  63. package/src/content/docs/models.mdx +223 -0
  64. package/src/content/docs/modes.mdx +331 -0
  65. package/src/content/docs/network.mdx +57 -0
  66. package/src/content/docs/permissions.mdx +237 -0
  67. package/src/content/docs/plugins.mdx +362 -0
  68. package/src/content/docs/providers.mdx +1889 -0
  69. package/src/content/docs/rules.mdx +180 -0
  70. package/src/content/docs/sdk.mdx +391 -0
  71. package/src/content/docs/server.mdx +286 -0
  72. package/src/content/docs/share.mdx +128 -0
  73. package/src/content/docs/skills.mdx +220 -0
  74. package/src/content/docs/themes.mdx +369 -0
  75. package/src/content/docs/tools.mdx +345 -0
  76. package/src/content/docs/troubleshooting.mdx +300 -0
  77. package/src/content/docs/tui.mdx +390 -0
  78. package/src/content/docs/web.mdx +136 -0
  79. package/src/content/docs/windows-wsl.mdx +113 -0
  80. package/src/content/docs/zen.mdx +251 -0
  81. package/src/content.config.ts +7 -0
  82. package/src/pages/[...slug].md.ts +18 -0
  83. package/src/pages/s/[id].astro +113 -0
  84. package/src/styles/custom.css +405 -0
  85. package/src/types/lang-map.d.ts +27 -0
  86. package/tsconfig.json +9 -0
@@ -0,0 +1,77 @@
1
+ import { createSignal, onCleanup, splitProps } from "solid-js"
2
+ import type { JSX } from "solid-js/jsx-runtime"
3
+ import { IconCheckCircle, IconHashtag } from "../icons"
4
+
5
+ interface AnchorProps extends JSX.HTMLAttributes<HTMLDivElement> {
6
+ id: string
7
+ }
8
+ export function AnchorIcon(props: AnchorProps) {
9
+ const [local, rest] = splitProps(props, ["id", "children"])
10
+ const [copied, setCopied] = createSignal(false)
11
+
12
+ return (
13
+ <div {...rest} data-element-anchor title="Link to this message" data-status={copied() ? "copied" : ""}>
14
+ <a
15
+ href={`#${local.id}`}
16
+ onClick={(e) => {
17
+ e.preventDefault()
18
+
19
+ const anchor = e.currentTarget
20
+ const hash = anchor.getAttribute("href") || ""
21
+ const { origin, pathname, search } = window.location
22
+
23
+ navigator.clipboard
24
+ .writeText(`${origin}${pathname}${search}${hash}`)
25
+ .catch((err) => console.error("Copy failed", err))
26
+
27
+ setCopied(true)
28
+ setTimeout(() => setCopied(false), 3000)
29
+ }}
30
+ >
31
+ {local.children}
32
+ <IconHashtag width={18} height={18} />
33
+ <IconCheckCircle width={18} height={18} />
34
+ </a>
35
+ <span data-element-tooltip>Copied!</span>
36
+ </div>
37
+ )
38
+ }
39
+
40
+ export function createOverflow() {
41
+ const [overflow, setOverflow] = createSignal(false)
42
+ return {
43
+ get status() {
44
+ return overflow()
45
+ },
46
+ ref(el: HTMLElement) {
47
+ const ro = new ResizeObserver(() => {
48
+ if (el.scrollHeight > el.clientHeight + 1) {
49
+ setOverflow(true)
50
+ }
51
+ return
52
+ })
53
+ ro.observe(el)
54
+
55
+ onCleanup(() => {
56
+ ro.disconnect()
57
+ })
58
+ },
59
+ }
60
+ }
61
+
62
+ export function formatDuration(ms: number): string {
63
+ const ONE_SECOND = 1000
64
+ const ONE_MINUTE = 60 * ONE_SECOND
65
+
66
+ if (ms >= ONE_MINUTE) {
67
+ const minutes = Math.floor(ms / ONE_MINUTE)
68
+ return minutes === 1 ? `1min` : `${minutes}mins`
69
+ }
70
+
71
+ if (ms >= ONE_SECOND) {
72
+ const seconds = Math.floor(ms / ONE_SECOND)
73
+ return `${seconds}s`
74
+ }
75
+
76
+ return `${ms}ms`
77
+ }
@@ -0,0 +1,85 @@
1
+ .root {
2
+ display: contents;
3
+
4
+ [data-slot="expand-button"] {
5
+ flex: 0 0 auto;
6
+ padding: 2px 0;
7
+ font-size: 0.75rem;
8
+ }
9
+
10
+ [data-slot="body"] {
11
+ border: 1px solid var(--sl-color-divider);
12
+ border-radius: 0.25rem;
13
+ overflow: hidden;
14
+ width: 100%;
15
+ }
16
+
17
+ [data-slot="header"] {
18
+ position: relative;
19
+ border-bottom: 1px solid var(--sl-color-divider);
20
+ width: 100%;
21
+ height: 1.625rem;
22
+ text-align: center;
23
+ padding: 0 3.25rem;
24
+
25
+ > span {
26
+ max-width: min(100%, 140ch);
27
+ display: inline-block;
28
+ white-space: nowrap;
29
+ overflow: hidden;
30
+ line-height: 1.625rem;
31
+ font-size: 0.75rem;
32
+ text-overflow: ellipsis;
33
+ color: var(--sl-color-text-dimmed);
34
+ }
35
+
36
+ &::before {
37
+ content: "";
38
+ position: absolute;
39
+ pointer-events: none;
40
+ top: 8px;
41
+ left: 10px;
42
+ width: 2rem;
43
+ height: 0.5rem;
44
+ line-height: 0;
45
+ background-color: var(--sl-color-hairline);
46
+ mask-image: var(--term-icon);
47
+ mask-repeat: no-repeat;
48
+ }
49
+ }
50
+
51
+ [data-slot="content"] {
52
+ display: flex;
53
+ flex-direction: column;
54
+ padding: 0.5rem calc(0.5rem + 3px);
55
+
56
+ pre {
57
+ --shiki-dark-bg: var(--sl-color-bg) !important;
58
+ background-color: var(--sl-color-bg) !important;
59
+ line-height: 1.6;
60
+ font-size: 0.75rem;
61
+ white-space: pre-wrap;
62
+ word-break: break-word;
63
+ margin: 0;
64
+
65
+ span {
66
+ white-space: break-spaces;
67
+ }
68
+ }
69
+ }
70
+
71
+ [data-slot="output"] {
72
+ display: -webkit-box;
73
+ -webkit-box-orient: vertical;
74
+ -webkit-line-clamp: 10;
75
+ line-clamp: 10;
76
+ overflow: hidden;
77
+ }
78
+
79
+ &[data-expanded] [data-slot="output"] {
80
+ display: block;
81
+ -webkit-line-clamp: none;
82
+ line-clamp: none;
83
+ overflow: visible;
84
+ }
85
+ }
@@ -0,0 +1,67 @@
1
+ import style from "./content-bash.module.css"
2
+ import { createResource, createSignal } from "solid-js"
3
+ import { createOverflow } from "./common"
4
+ import { codeToHtml } from "shiki"
5
+
6
+ interface Props {
7
+ command: string
8
+ output: string
9
+ description?: string
10
+ expand?: boolean
11
+ }
12
+
13
+ export function ContentBash(props: Props) {
14
+ const [commandHtml] = createResource(
15
+ () => props.command,
16
+ async (command) => {
17
+ return codeToHtml(command || "", {
18
+ lang: "bash",
19
+ themes: {
20
+ light: "github-light",
21
+ dark: "github-dark",
22
+ },
23
+ })
24
+ },
25
+ )
26
+
27
+ const [outputHtml] = createResource(
28
+ () => props.output,
29
+ async (output) => {
30
+ return codeToHtml(output || "", {
31
+ lang: "console",
32
+ themes: {
33
+ light: "github-light",
34
+ dark: "github-dark",
35
+ },
36
+ })
37
+ },
38
+ )
39
+
40
+ const [expanded, setExpanded] = createSignal(false)
41
+ const overflow = createOverflow()
42
+
43
+ return (
44
+ <div class={style.root} data-expanded={expanded() || props.expand === true ? true : undefined}>
45
+ <div data-slot="body">
46
+ <div data-slot="header">
47
+ <span>{props.description}</span>
48
+ </div>
49
+ <div data-slot="content">
50
+ <div innerHTML={commandHtml()} />
51
+ <div data-slot="output" ref={overflow.ref} innerHTML={outputHtml()} />
52
+ </div>
53
+ </div>
54
+
55
+ {!props.expand && overflow.status && (
56
+ <button
57
+ type="button"
58
+ data-component="text-button"
59
+ data-slot="expand-button"
60
+ onClick={() => setExpanded((e) => !e)}
61
+ >
62
+ {expanded() ? "Show less" : "Show more"}
63
+ </button>
64
+ )}
65
+ </div>
66
+ )
67
+ }
@@ -0,0 +1,26 @@
1
+ .root {
2
+ border: 1px solid var(--sl-color-divider);
3
+ background-color: var(--sl-color-bg-surface);
4
+ border-radius: 0.25rem;
5
+ padding: 0.5rem calc(0.5rem + 3px);
6
+
7
+ &[data-flush="true"] {
8
+ border: none;
9
+ background-color: transparent;
10
+ padding: 0;
11
+ border-radius: 0;
12
+ }
13
+
14
+ pre {
15
+ --shiki-dark-bg: var(--sl-color-bg-surface) !important;
16
+ background-color: var(--sl-color-bg-surface) !important;
17
+ line-height: 1.6;
18
+ font-size: 0.75rem;
19
+ white-space: pre-wrap;
20
+ word-break: break-word;
21
+
22
+ span {
23
+ white-space: break-spaces;
24
+ }
25
+ }
26
+ }
@@ -0,0 +1,32 @@
1
+ import { codeToHtml, bundledLanguages } from "shiki"
2
+ import { createResource, Suspense } from "solid-js"
3
+ import { transformerNotationDiff } from "@shikijs/transformers"
4
+ import style from "./content-code.module.css"
5
+
6
+ interface Props {
7
+ code: string
8
+ lang?: string
9
+ flush?: boolean
10
+ }
11
+ export function ContentCode(props: Props) {
12
+ const [html] = createResource(
13
+ () => [props.code, props.lang],
14
+ async ([code, lang]) => {
15
+ // TODO: For testing delays
16
+ // await new Promise((resolve) => setTimeout(resolve, 3000))
17
+ return (await codeToHtml(code || "", {
18
+ lang: lang && lang in bundledLanguages ? lang : "text",
19
+ themes: {
20
+ light: "github-light",
21
+ dark: "github-dark",
22
+ },
23
+ transformers: [transformerNotationDiff()],
24
+ })) as string
25
+ },
26
+ )
27
+ return (
28
+ <Suspense>
29
+ <div innerHTML={html()} class={style.root} data-flush={props.flush === true ? true : undefined} />
30
+ </Suspense>
31
+ )
32
+ }
@@ -0,0 +1,153 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ border: 1px solid var(--sl-color-divider);
5
+ background-color: var(--sl-color-bg-surface);
6
+ border-radius: 0.25rem;
7
+
8
+ [data-component="desktop"] {
9
+ display: block;
10
+ }
11
+
12
+ [data-component="mobile"] {
13
+ display: none;
14
+ }
15
+
16
+ [data-component="diff-block"] {
17
+ display: flex;
18
+ flex-direction: column;
19
+ }
20
+
21
+ [data-component="diff-row"] {
22
+ display: grid;
23
+ grid-template-columns: 1fr 1fr;
24
+ align-items: stretch;
25
+
26
+ &:first-child {
27
+ [data-slot="before"],
28
+ [data-slot="after"] {
29
+ padding-top: 0.25rem;
30
+ }
31
+ }
32
+
33
+ &:last-child {
34
+ [data-slot="before"],
35
+ [data-slot="after"] {
36
+ padding-bottom: 0.25rem;
37
+ }
38
+ }
39
+
40
+ [data-slot="before"],
41
+ [data-slot="after"] {
42
+ position: relative;
43
+ display: flex;
44
+ flex-direction: column;
45
+ overflow-x: visible;
46
+ min-width: 0;
47
+ align-items: stretch;
48
+ padding: 0 1rem 0 2.2ch;
49
+
50
+ &[data-diff-type="removed"] {
51
+ background-color: var(--sl-color-red-low);
52
+
53
+ pre {
54
+ --shiki-dark-bg: var(--sl-color-red-low) !important;
55
+ background-color: var(--sl-color-red-low) !important;
56
+ }
57
+
58
+ &::before {
59
+ content: "-";
60
+ position: absolute;
61
+ left: 0.6ch;
62
+ top: 1px;
63
+ user-select: none;
64
+ color: var(--sl-color-red-high);
65
+ }
66
+ }
67
+
68
+ &[data-diff-type="added"] {
69
+ background-color: var(--sl-color-green-low);
70
+
71
+ pre {
72
+ --shiki-dark-bg: var(--sl-color-green-low) !important;
73
+ background-color: var(--sl-color-green-low) !important;
74
+ }
75
+
76
+ &::before {
77
+ content: "+";
78
+ position: absolute;
79
+ user-select: none;
80
+ color: var(--sl-color-green-high);
81
+ left: 0.6ch;
82
+ top: 1px;
83
+ }
84
+ }
85
+ }
86
+
87
+ [data-slot="before"] {
88
+ border-right: 1px solid var(--sl-color-divider);
89
+ }
90
+ }
91
+
92
+ [data-component="mobile"] {
93
+ & > [data-component="diff-block"]:first-child > div {
94
+ padding-top: 0.25rem;
95
+ }
96
+
97
+ & > [data-component="diff-block"]:last-child > div {
98
+ padding-bottom: 0.25rem;
99
+ }
100
+
101
+ & > [data-component="diff-block"] > div {
102
+ padding: 0 1rem 0 2.2ch;
103
+
104
+ &[data-diff-type="removed"] {
105
+ position: relative;
106
+ background-color: var(--sl-color-red-low);
107
+
108
+ pre {
109
+ --shiki-dark-bg: var(--sl-color-red-low) !important;
110
+ background-color: var(--sl-color-red-low) !important;
111
+ }
112
+
113
+ &::before {
114
+ content: "-";
115
+ position: absolute;
116
+ left: 0.6ch;
117
+ top: 1px;
118
+ user-select: none;
119
+ color: var(--sl-color-red-high);
120
+ }
121
+ }
122
+
123
+ &[data-diff-type="added"] {
124
+ position: relative;
125
+ background-color: var(--sl-color-green-low);
126
+
127
+ pre {
128
+ --shiki-dark-bg: var(--sl-color-green-low) !important;
129
+ background-color: var(--sl-color-green-low) !important;
130
+ }
131
+
132
+ &::before {
133
+ content: "+";
134
+ position: absolute;
135
+ left: 0.6ch;
136
+ top: 1px;
137
+ user-select: none;
138
+ color: var(--sl-color-green-high);
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ @media (max-width: 40rem) {
145
+ [data-component="desktop"] {
146
+ display: none;
147
+ }
148
+
149
+ [data-component="mobile"] {
150
+ display: block;
151
+ }
152
+ }
153
+ }
@@ -0,0 +1,231 @@
1
+ import { parsePatch } from "diff"
2
+ import { createMemo } from "solid-js"
3
+ import { ContentCode } from "./content-code"
4
+ import styles from "./content-diff.module.css"
5
+
6
+ type DiffRow = {
7
+ left: string
8
+ right: string
9
+ type: "added" | "removed" | "unchanged" | "modified"
10
+ }
11
+
12
+ interface Props {
13
+ diff: string
14
+ lang?: string
15
+ }
16
+
17
+ export function ContentDiff(props: Props) {
18
+ const rows = createMemo(() => {
19
+ const diffRows: DiffRow[] = []
20
+
21
+ try {
22
+ const patches = parsePatch(props.diff)
23
+
24
+ for (const patch of patches) {
25
+ for (const hunk of patch.hunks) {
26
+ const lines = hunk.lines
27
+ let i = 0
28
+
29
+ while (i < lines.length) {
30
+ const line = lines[i]
31
+ const content = line.slice(1)
32
+ const prefix = line[0]
33
+
34
+ if (prefix === "-") {
35
+ // Look ahead for consecutive additions to pair with removals
36
+ const removals: string[] = [content]
37
+ let j = i + 1
38
+
39
+ // Collect all consecutive removals
40
+ while (j < lines.length && lines[j][0] === "-") {
41
+ removals.push(lines[j].slice(1))
42
+ j++
43
+ }
44
+
45
+ // Collect all consecutive additions that follow
46
+ const additions: string[] = []
47
+ while (j < lines.length && lines[j][0] === "+") {
48
+ additions.push(lines[j].slice(1))
49
+ j++
50
+ }
51
+
52
+ // Pair removals with additions
53
+ const maxLength = Math.max(removals.length, additions.length)
54
+ for (let k = 0; k < maxLength; k++) {
55
+ const hasLeft = k < removals.length
56
+ const hasRight = k < additions.length
57
+
58
+ if (hasLeft && hasRight) {
59
+ // Replacement - left is removed, right is added
60
+ diffRows.push({
61
+ left: removals[k],
62
+ right: additions[k],
63
+ type: "modified",
64
+ })
65
+ } else if (hasLeft) {
66
+ // Pure removal
67
+ diffRows.push({
68
+ left: removals[k],
69
+ right: "",
70
+ type: "removed",
71
+ })
72
+ } else if (hasRight) {
73
+ // Pure addition - only create if we actually have content
74
+ diffRows.push({
75
+ left: "",
76
+ right: additions[k],
77
+ type: "added",
78
+ })
79
+ }
80
+ }
81
+
82
+ i = j
83
+ } else if (prefix === "+") {
84
+ // Standalone addition (not paired with removal)
85
+ diffRows.push({
86
+ left: "",
87
+ right: content,
88
+ type: "added",
89
+ })
90
+ i++
91
+ } else if (prefix === " ") {
92
+ diffRows.push({
93
+ left: content === "" ? " " : content,
94
+ right: content === "" ? " " : content,
95
+ type: "unchanged",
96
+ })
97
+ i++
98
+ } else {
99
+ i++
100
+ }
101
+ }
102
+ }
103
+ }
104
+ } catch (error) {
105
+ console.error("Failed to parse patch:", error)
106
+ return []
107
+ }
108
+
109
+ return diffRows
110
+ })
111
+
112
+ const mobileRows = createMemo(() => {
113
+ const mobileBlocks: {
114
+ type: "removed" | "added" | "unchanged"
115
+ lines: string[]
116
+ }[] = []
117
+ const currentRows = rows()
118
+
119
+ let i = 0
120
+ while (i < currentRows.length) {
121
+ const removedLines: string[] = []
122
+ const addedLines: string[] = []
123
+
124
+ // Collect consecutive modified/removed/added rows
125
+ while (
126
+ i < currentRows.length &&
127
+ (currentRows[i].type === "modified" || currentRows[i].type === "removed" || currentRows[i].type === "added")
128
+ ) {
129
+ const row = currentRows[i]
130
+ if (row.left && (row.type === "removed" || row.type === "modified")) {
131
+ removedLines.push(row.left)
132
+ }
133
+ if (row.right && (row.type === "added" || row.type === "modified")) {
134
+ addedLines.push(row.right)
135
+ }
136
+ i++
137
+ }
138
+
139
+ // Add grouped blocks
140
+ if (removedLines.length > 0) {
141
+ mobileBlocks.push({ type: "removed", lines: removedLines })
142
+ }
143
+ if (addedLines.length > 0) {
144
+ mobileBlocks.push({ type: "added", lines: addedLines })
145
+ }
146
+
147
+ // Add unchanged rows as-is
148
+ if (i < currentRows.length && currentRows[i].type === "unchanged") {
149
+ mobileBlocks.push({
150
+ type: "unchanged",
151
+ lines: [currentRows[i].left],
152
+ })
153
+ i++
154
+ }
155
+ }
156
+
157
+ return mobileBlocks
158
+ })
159
+
160
+ return (
161
+ <div class={styles.root}>
162
+ <div data-component="desktop">
163
+ {rows().map((r) => (
164
+ <div data-component="diff-row" data-type={r.type}>
165
+ <div data-slot="before" data-diff-type={r.type === "removed" || r.type === "modified" ? "removed" : ""}>
166
+ <ContentCode code={r.left} flush lang={props.lang} />
167
+ </div>
168
+ <div data-slot="after" data-diff-type={r.type === "added" || r.type === "modified" ? "added" : ""}>
169
+ <ContentCode code={r.right} lang={props.lang} flush />
170
+ </div>
171
+ </div>
172
+ ))}
173
+ </div>
174
+
175
+ <div data-component="mobile">
176
+ {mobileRows().map((block) => (
177
+ <div data-component="diff-block" data-type={block.type}>
178
+ {block.lines.map((line) => (
179
+ <div data-diff-type={block.type === "removed" ? "removed" : block.type === "added" ? "added" : ""}>
180
+ <ContentCode code={line} lang={props.lang} flush />
181
+ </div>
182
+ ))}
183
+ </div>
184
+ ))}
185
+ </div>
186
+ </div>
187
+ )
188
+ }
189
+
190
+ // const testDiff = `--- combined_before.txt 2025-06-24 16:38:08
191
+ // +++ combined_after.txt 2025-06-24 16:38:12
192
+ // @@ -1,21 +1,25 @@
193
+ // unchanged line
194
+ // -deleted line
195
+ // -old content
196
+ // +added line
197
+ // +new content
198
+ //
199
+ // -removed empty line below
200
+ // +added empty line above
201
+ //
202
+ // - tab indented
203
+ // -trailing spaces
204
+ // -very long line that will definitely wrap in most editors and cause potential alignment issues when displayed in a two column diff view
205
+ // -unicode content: πŸš€ ✨ δΈ­ζ–‡
206
+ // -mixed content with tabs and spaces
207
+ // + space indented
208
+ // +no trailing spaces
209
+ // +short line
210
+ // +very long replacement line that will also wrap and test how the diff viewer handles long line additions after short line removals
211
+ // +different unicode: πŸŽ‰ πŸ’» ζ—₯本θͺž
212
+ // +normalized content with consistent spacing
213
+ // +newline to content
214
+ //
215
+ // -content to remove
216
+ // -whitespace only:
217
+ // -multiple
218
+ // -consecutive
219
+ // -deletions
220
+ // -single deletion
221
+ // +
222
+ // +single addition
223
+ // +first addition
224
+ // +second addition
225
+ // +third addition
226
+ // line before addition
227
+ // +first added line
228
+ // +
229
+ // +third added line
230
+ // line after addition
231
+ // final unchanged line`