@brett_lamy/docstream 0.5.7 → 0.6.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.
package/README.md CHANGED
@@ -32,6 +32,10 @@ Import the package CSS once near your app entrypoint:
32
32
  import "@brett_lamy/docstream/styles.css"
33
33
  ```
34
34
 
35
+ The stylesheet provides heading hierarchy and consistent spacing for the
36
+ `DocsRenderer`, `MarkdownContent`, and streaming render paths. Host CSS can
37
+ override those defaults through the normal cascade.
38
+
35
39
  If your TypeScript app checks CSS side-effect imports, include Vite's standard environment declaration or an equivalent CSS module declaration:
36
40
 
37
41
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brett_lamy/docstream",
3
- "version": "0.5.7",
3
+ "version": "0.6.1",
4
4
  "description": "GitBook-aware readonly markdown and AI stream renderer.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -376,7 +376,11 @@ function isReactLanguage(language: string | null): boolean {
376
376
 
377
377
  function Blocks({ blocks, inline, liveRenderer, sourceRenderer }: { blocks: Block[]; inline?: boolean } & Renderers) {
378
378
  return (
379
- <div className={inline ? "docs-blocks-inline" : undefined} style={inline ? { display: "inline" } : undefined}>
379
+ <div
380
+ data-docstream-blocks=""
381
+ className={inline ? "docs-blocks-inline" : undefined}
382
+ style={inline ? { display: "inline" } : undefined}
383
+ >
380
384
  {blocks.map((b, i) => (
381
385
  <BlockView key={i} block={b} liveRenderer={liveRenderer} sourceRenderer={sourceRenderer} />
382
386
  ))}
@@ -1,5 +1,5 @@
1
1
  import { createContext, useContext, useId, useState, type ReactNode } from "react"
2
- import { ArrowUpRight, AtSign, Globe, Hash } from "lucide-react"
2
+ import { ArrowUpRight, AtSign, FolderGit2, Globe, Hash } from "lucide-react"
3
3
 
4
4
  import type { CitationDef, ReferenceNode } from "../gitbook/ast"
5
5
 
@@ -112,7 +112,7 @@ export function InlineReference({ node }: { node: ReferenceNode }) {
112
112
  )
113
113
  }
114
114
 
115
- const Icon = node.kind === "mention" ? AtSign : Hash
115
+ const Icon = node.kind === "mention" ? AtSign : node.kind === "codebase" ? FolderGit2 : Hash
116
116
  return (
117
117
  <button
118
118
  type="button"
@@ -23,7 +23,7 @@ export interface InlineImageNode {
23
23
  link?: string
24
24
  }
25
25
 
26
- export type ReferenceKind = "mention" | "tag" | "citation"
26
+ export type ReferenceKind = "mention" | "tag" | "codebase" | "citation"
27
27
 
28
28
  /**
29
29
  * Inline reference chip: `@mention`, `#tag`, or footnote-style citation `[^id]`.
@@ -10,8 +10,13 @@ export const refDefinitions = new Map<string, string>()
10
10
  // parseMarkdown and consumed here to resolve [^id] markers.
11
11
  export const footnoteDefinitions = new Map<string, { url: string; label?: string }>()
12
12
 
13
- // @mention / #tag body: letter/underscore start, then word chars, dots, dashes.
13
+ // @mention / #tag / $codebase body: letter/underscore start, then word chars, dots,
14
+ // dashes. Codebase ids may also carry `/` (`$org/repo`). A letter start keeps prices
15
+ // like $5 plain text.
14
16
  const REFERENCE_BODY_RE = /^[@#]([A-Za-z_][\w.-]*)/
17
+ const CODEBASE_BODY_RE = /^\$([A-Za-z_][\w.\/-]*)/
18
+ const SIGIL_KIND = { "@": "mention", "#": "tag", $: "codebase" } as const
19
+ const KIND_SIGIL = { mention: "@", tag: "#", codebase: "$" } as const
15
20
  // Chips are only recognized at start-of-input or after whitespace/open brackets,
16
21
  // so brett@replay.io and C# stay plain text.
17
22
  const isReferenceBoundary = (prev: string | undefined) =>
@@ -131,14 +136,14 @@ export function parseInline(src: string, marks: Marks = {}): Inline[] {
131
136
  continue
132
137
  }
133
138
 
134
- // @mention / #tag chips, only at a word boundary
135
- if ((rest[0] === "@" || rest[0] === "#") && isReferenceBoundary(src[i - 1])) {
136
- const m = rest.match(REFERENCE_BODY_RE)
139
+ // @mention / #tag / $codebase chips, only at a word boundary
140
+ if ((rest[0] === "@" || rest[0] === "#" || rest[0] === "$") && isReferenceBoundary(src[i - 1])) {
141
+ const m = rest.match(rest[0] === "$" ? CODEBASE_BODY_RE : REFERENCE_BODY_RE)
137
142
  if (m) {
138
- const id = m[1].replace(/[.-]+$/, "")
143
+ const id = m[1].replace(/[./-]+$/, "")
139
144
  if (id) {
140
145
  flush()
141
- out.push({ type: "reference", kind: rest[0] === "@" ? "mention" : "tag", id })
146
+ out.push({ type: "reference", kind: SIGIL_KIND[rest[0] as "@" | "#" | "$"], id })
142
147
  i += 1 + id.length
143
148
  continue
144
149
  }
@@ -200,14 +205,14 @@ export function parseInline(src: string, marks: Marks = {}): Inline[] {
200
205
  const escapeText = (t: string) =>
201
206
  t
202
207
  .replace(/([*_~`[\]\\])/g, "\\$1")
203
- // Escape @/# only at a chip boundary so emails/C# survive untouched.
208
+ // Escape @/#/$ only at a chip boundary so emails/C#/$5 survive untouched.
204
209
  // Note: node-local — a boundary formed across adjacent inline nodes
205
210
  // (previous node ending in whitespace) is not caught; rare, accepted.
206
- .replace(/(^|[\s([{])([@#])(?=[A-Za-z_])/g, "$1\\$2")
211
+ .replace(/(^|[\s([{])([@#$])(?=[A-Za-z_])/g, "$1\\$2")
207
212
 
208
213
  export function serializeReference(n: ReferenceNode): string {
209
214
  if (n.kind === "citation") return `[^${n.id}]`
210
- return `${n.kind === "mention" ? "@" : "#"}${n.id}`
215
+ return `${KIND_SIGIL[n.kind]}${n.id}`
211
216
  }
212
217
 
213
218
  function serializeImage(n: InlineImageNode): string {
package/src/styles.css CHANGED
@@ -68,44 +68,87 @@
68
68
  white-space: pre-wrap;
69
69
  }
70
70
 
71
+ :is([data-docstream], .docs-article) {
72
+ color: var(--foreground, #18181b);
73
+ line-height: 1.65;
74
+ font-size: 15.5px;
75
+ }
76
+
71
77
  .docs-article {
72
78
  max-width: 760px;
73
79
  margin: 0 auto;
74
80
  padding: 48px 32px 120px;
75
- line-height: 1.65;
76
- font-size: 15.5px;
77
81
  }
78
82
 
79
- .docs-article > div > * + * {
80
- margin-top: 0.85em;
83
+ :is([data-docstream], .docs-article) [data-docstream-blocks] > * {
84
+ margin-block: 0;
85
+ }
86
+
87
+ :is([data-docstream], .docs-article) [data-docstream-blocks] > * + * {
88
+ margin-block-start: 0.9em;
89
+ }
90
+
91
+ :is([data-docstream], .docs-article) :is(h1, h2, h3, h4, h5, h6) {
92
+ color: inherit;
93
+ display: block;
94
+ font-family: inherit;
95
+ font-weight: 650;
96
+ line-height: 1.25;
97
+ margin-block: 1.6em 0.45em;
98
+ text-wrap: pretty;
99
+ }
100
+
101
+ :is([data-docstream], .docs-article) [data-docstream-blocks] > :is(h1, h2, h3, h4, h5, h6) {
102
+ margin-block-start: 1.6em;
81
103
  }
82
104
 
83
- .docs-article h1 {
105
+ :is([data-docstream], .docs-article) [data-docstream-blocks] > :first-child:is(h1, h2, h3, h4, h5, h6) {
106
+ margin-block-start: 0;
107
+ }
108
+
109
+ :is([data-docstream], .docs-article) h1 {
84
110
  font-size: 2.2em;
85
111
  font-weight: 700;
86
- margin-top: 0.6em;
87
112
  }
88
113
 
89
- .docs-article h2 {
114
+ :is([data-docstream], .docs-article) h2 {
90
115
  font-size: 1.5em;
91
- font-weight: 650;
92
- margin-top: 1.4em;
116
+ font-weight: 675;
93
117
  padding-bottom: 0.2em;
94
118
  border-bottom: 1px solid var(--border, #e4e4e7);
95
119
  }
96
120
 
97
- .docs-article h3 {
121
+ :is([data-docstream], .docs-article) h3 {
98
122
  font-size: 1.2em;
99
123
  font-weight: 600;
100
- margin-top: 1.1em;
101
124
  }
102
125
 
103
- .docs-article a {
126
+ :is([data-docstream], .docs-article) h4 {
127
+ font-size: 1.08em;
128
+ font-weight: 600;
129
+ }
130
+
131
+ :is([data-docstream], .docs-article) h5 {
132
+ font-size: 1em;
133
+ font-weight: 600;
134
+ }
135
+
136
+ :is([data-docstream], .docs-article) h6 {
137
+ font-size: 0.92em;
138
+ font-weight: 600;
139
+ letter-spacing: 0.02em;
140
+ }
141
+
142
+ :is([data-docstream], .docs-article) p {
143
+ margin-block: 0;
144
+ }
145
+
146
+ :is([data-docstream], .docs-article) a {
104
147
  color: var(--gb-primary);
105
148
  text-decoration: underline;
106
149
  }
107
150
 
108
- .docs-article code {
151
+ :is([data-docstream], .docs-article) code {
109
152
  background: var(--gb-muted);
110
153
  border-radius: 4px;
111
154
  padding: 0.15em 0.35em;
@@ -113,12 +156,12 @@
113
156
  font-family: ui-monospace, Menlo, monospace;
114
157
  }
115
158
 
116
- .docs-article ul {
159
+ :is([data-docstream], .docs-article) ul {
117
160
  list-style: disc;
118
161
  padding-left: 1.5em;
119
162
  }
120
163
 
121
- .docs-article ol {
164
+ :is([data-docstream], .docs-article) ol {
122
165
  list-style: decimal;
123
166
  padding-left: 1.5em;
124
167
  }
@@ -148,24 +191,24 @@
148
191
  margin: 0;
149
192
  }
150
193
 
151
- .docs-article blockquote {
194
+ :is([data-docstream], .docs-article) blockquote {
152
195
  border-left: 3px solid var(--gb-border);
153
196
  padding-left: 1em;
154
197
  color: var(--gb-muted-foreground);
155
198
  }
156
199
 
157
- .docs-article hr {
200
+ :is([data-docstream], .docs-article) hr {
158
201
  border: none;
159
202
  border-top: 1px solid var(--gb-border);
160
203
  margin: 2em 0;
161
204
  }
162
205
 
163
- .docs-article figure img {
206
+ :is([data-docstream], .docs-article) figure img {
164
207
  border-radius: 12px;
165
208
  max-width: 100%;
166
209
  }
167
210
 
168
- .docs-article figcaption {
211
+ :is([data-docstream], .docs-article) figcaption {
169
212
  text-align: center;
170
213
  font-size: 13px;
171
214
  color: var(--gb-muted-foreground);
@@ -1073,6 +1116,16 @@
1073
1116
  color: var(--gb-accent-foreground);
1074
1117
  }
1075
1118
 
1119
+ .docs-ref-codebase {
1120
+ border: 1px solid rgba(34, 197, 94, 0.45);
1121
+ background: rgba(34, 197, 94, 0.1);
1122
+ color: rgb(74, 222, 128);
1123
+ }
1124
+
1125
+ .docs-ref-codebase:hover {
1126
+ background: rgba(34, 197, 94, 0.18);
1127
+ }
1128
+
1076
1129
  .docs-cite-wrap {
1077
1130
  position: relative;
1078
1131
  display: inline-block;