@deepseek-ai/dsh-client-ui-primitives 0.1.1-rc.2 → 0.1.2-alpha.3

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 (47) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +91 -24
  3. package/README.zh.md +92 -25
  4. package/lib/ConnectionIndicator.module.css +107 -0
  5. package/lib/DiffBlock.module.css +0 -6
  6. package/lib/DisclosureRow.module.css +21 -6
  7. package/lib/ReadBlock.module.css +0 -6
  8. package/lib/SearchBlock.module.css +0 -8
  9. package/lib/TerminalBlock.module.css +0 -6
  10. package/lib/Toast.module.css +7 -10
  11. package/lib/Tooltip.module.css +0 -7
  12. package/lib/WebBlock.module.css +0 -6
  13. package/lib/index.js +1243 -352
  14. package/lib/markdown/CodeBlock.module.css +4 -2
  15. package/lib/markdown/MarkdownText.module.css +2 -2
  16. package/lib/types/ConnectionIndicator.d.ts +25 -0
  17. package/lib/types/DiffBlock.d.ts +28 -9
  18. package/lib/types/FishLogo.d.ts +7 -0
  19. package/lib/types/FoldToggle.d.ts +20 -0
  20. package/lib/types/HoverCard.d.ts +4 -4
  21. package/lib/types/JsonTree.d.ts +4 -6
  22. package/lib/types/Modal.d.ts +22 -18
  23. package/lib/types/OnboardingSurface.d.ts +2 -2
  24. package/lib/types/ReadBlock.d.ts +13 -1
  25. package/lib/types/ReferenceIcon.d.ts +16 -0
  26. package/lib/types/RiskConfirmation.d.ts +2 -1
  27. package/lib/types/SearchBlock.d.ts +14 -0
  28. package/lib/types/TerminalBlock.d.ts +4 -10
  29. package/lib/types/Toast.d.ts +9 -1
  30. package/lib/types/WebBlock.d.ts +13 -0
  31. package/lib/types/icons/index.d.ts +8 -0
  32. package/lib/types/index.d.ts +14 -8
  33. package/lib/types/markdown/CodeBlock.d.ts +12 -3
  34. package/lib/types/markdown/JsonBlock.d.ts +1 -1
  35. package/lib/types/markdown/MarkdownText.d.ts +8 -7
  36. package/lib/types/markdown/highlight.d.ts +69 -2
  37. package/lib/types/markdown/incremental.d.ts +23 -12
  38. package/lib/types/markdown/render.d.ts +9 -4
  39. package/lib/types/markdown/useViewportHighlighting.d.ts +11 -0
  40. package/lib/types/relative-time.d.ts +23 -0
  41. package/lib/types/useAnchoredPosition.d.ts +8 -6
  42. package/lib/types/useDismissOnOutsidePointer.d.ts +3 -1
  43. package/lib/types/user-text.d.ts +19 -0
  44. package/lib/user-text.module.css +35 -0
  45. package/package.json +4 -5
  46. package/lib/ConnectionBanner.module.css +0 -13
  47. package/lib/types/ConnectionBanner.d.ts +0 -12
@@ -18,6 +18,12 @@
18
18
  * highlighting, still monospace) — never an error.
19
19
  */
20
20
  import type { CSSProperties } from 'react';
21
+ /**
22
+ * Whether a language hint can use the shared syntax highlighter.
23
+ * @param lang - Language hint from a code surface.
24
+ * @returns Whether the hint resolves to a supported grammar.
25
+ */
26
+ export declare function supportsHighlighting(lang: string | undefined): boolean;
21
27
  /**
22
28
  * Subscribe to lazy-grammar load completions; `listener` fires after a
23
29
  * {@link LAZY_GRAMMARS} grammar finishes registering on the singleton, so a
@@ -56,6 +62,66 @@ export interface HighlightSpan {
56
62
  text: string;
57
63
  style: CSSProperties;
58
64
  }
65
+ /**
66
+ * Incremental highlighter for one growing streaming fence. TextMate
67
+ * tokenization is line-based and forward-only — a line's tokens depend only on
68
+ * its own text and the grammar state entering it — so appended text never
69
+ * changes a completed line's tokens. The session caches the spans of every
70
+ * completed line together with the grammar state after them;
71
+ * {@link updateFrame} reports only newly completed lines plus the still-growing
72
+ * last line, while {@link update} materializes the complete compatibility
73
+ * result. Per-call tokenization cost therefore excludes the completed prefix,
74
+ * and the result equals a from-scratch tokenization of the same code.
75
+ * Non-append input and a change of resolved grammar reset the cache and
76
+ * re-tokenize fully, so any input stays correct.
77
+ */
78
+ export declare class StreamingHighlightSession {
79
+ /** Grammar id the cache was built with; a different resolution resets it. */
80
+ private resolved;
81
+ /** Newline-terminated source prefix covered by {@link spans}. */
82
+ private prefix;
83
+ /** Cached spans, one entry per completed line of {@link prefix}. */
84
+ private spans;
85
+ /** Grammar state after {@link prefix}; undefined = the grammar's initial state. */
86
+ private state;
87
+ private lastCode;
88
+ private lastLang;
89
+ private lastResult;
90
+ private generation;
91
+ private lastFrame;
92
+ private reset;
93
+ /** Tokenize `text` with `resolved`, resuming from the cached grammar state when one exists. */
94
+ private tokenize;
95
+ /**
96
+ * Tokenize one update as a delta for a retained renderer.
97
+ * @param code - the fence text accumulated so far.
98
+ * @param lang - the language hint.
99
+ * @returns Newly completed lines plus the current tail, or `undefined` for the plain arm.
100
+ */
101
+ updateFrame(code: string, lang: string | undefined): StreamingHighlightFrame | undefined;
102
+ /**
103
+ * Tokenize the fence's current text into per-line highlighted runs;
104
+ * `undefined` means the caller renders its plain fallback. Idempotent per
105
+ * (`code`, `lang`) input — repeated calls return the identical result array —
106
+ * and a retained line keeps its span-array identity across growing calls, so
107
+ * a React caller can reuse cached line elements. A lazy grammar not yet
108
+ * loaded returns `undefined` and loads in the background exactly as
109
+ * {@link highlightToHtml} does; the next call after it registers highlights.
110
+ * @param code - the fence text accumulated so far (display-trimmed, no synthetic trailing newline).
111
+ * @param lang - the language hint (a markdown fence info string).
112
+ * @returns one entry per line of `code` (each an array of runs), or `undefined` for unknown or not-yet-loaded languages.
113
+ */
114
+ update(code: string, lang: string | undefined): readonly HighlightSpan[][] | undefined;
115
+ }
116
+ /** One retained-renderer update from {@link StreamingHighlightSession.updateFrame}. */
117
+ export interface StreamingHighlightFrame {
118
+ /** Changes whenever prior completed lines must be discarded. */
119
+ readonly generation: number;
120
+ /** Completed lines added since the preceding frame in this generation. */
121
+ readonly appended: readonly HighlightSpan[][];
122
+ /** The still-growing final line or lines, replaced by the next frame. */
123
+ readonly tail: readonly HighlightSpan[][];
124
+ }
59
125
  /**
60
126
  * Tokenize `code` into per-line highlighted runs when `lang` maps to a
61
127
  * registered grammar; `undefined` means the caller renders its plain fallback.
@@ -64,8 +130,9 @@ export interface HighlightSpan {
64
130
  * so this returns shiki's own 2D line/token structure narrowed to what a run
65
131
  * renders. Each run's color is a `--shiki-*` custom property, keeping token
66
132
  * colors on the theme package's sheets exactly as the HTML path does; the
67
- * css-variables theme carries no font-style bits, matching that path's
68
- * color-only output. The trailing newline shiki appends as a final empty line
133
+ * markup font-style bits the theme lets through (bold/italic/underline in
134
+ * markdown scopes) are dropped — the line-numbered file view renders
135
+ * color-only runs. The trailing newline shiki appends as a final empty line
69
136
  * is dropped so the run count matches the caller's own line array.
70
137
  * @param code - the source text.
71
138
  * @param lang - the language hint (a file-extension-derived language id).
@@ -4,19 +4,23 @@
4
4
  * Re-parsing the whole accumulated document on every streaming chunk is
5
5
  * quadratic in the final reply length. CommonMark block parsing is line-based
6
6
  * and appended text can only reshape the parse frontier — the last top-level
7
- * block (a paragraph becoming a setext heading or a table, a list continuing
8
- * after a blank line, an unclosed fence swallowing lines) — so earlier blocks
9
- * are final. This parser therefore freezes all but the trailing
10
- * {@link UNSTABLE_TAIL_BLOCKS} blocks and re-parses only the source tail
11
- * behind them: each source region is parsed O(1) times over the stream
12
- * instead of once per chunk.
7
+ * block (a paragraph becoming a setext heading or a table, or a list
8
+ * continuing after a blank line) — so earlier blocks are final. This parser
9
+ * therefore freezes all but the trailing {@link UNSTABLE_TAIL_BLOCKS} blocks
10
+ * and re-parses only the source tail behind them. A final unclosed top-level
11
+ * fence cannot freeze as a block, so its completed content lines use a second
12
+ * frontier: only the last completed line and current partial line return
13
+ * through the caller's grammar. Each source region is therefore parsed a
14
+ * bounded number of times over the stream instead of once per chunk.
13
15
  *
14
- * The freeze boundary comes from the parser's own `position` offsets, never
15
- * from custom source scanning. The cut sits at the *end offset* of the last
16
- * frozen block (not the next block's start): a following block's start offset
17
- * excludes up to three spaces of insignificant leading indentation, which is
18
- * harmless to drop, but cutting at the previous end also keeps the
19
- * inter-block blank lines in the tail so the sliced source stays verbatim.
16
+ * The block freeze boundary comes from the parser's own `position` offsets.
17
+ * The cut sits at the *end offset* of the last frozen block (not the next
18
+ * block's start): a following block's start offset excludes up to three spaces
19
+ * of insignificant leading indentation, which is harmless to drop, but
20
+ * cutting at the previous end also keeps the inter-block blank lines in the
21
+ * tail so the sliced source stays verbatim. Fence scanning only recognizes a
22
+ * parser-confirmed code node and closing delimiter; ambiguous input returns to
23
+ * the normal tail parse.
20
24
  *
21
25
  * Known deviation, shared with any prefix-freeze scheme: micromark resolves
22
26
  * reference-style links and footnotes document-wide at parse time, so a
@@ -55,8 +59,15 @@ export declare class IncrementalMarkdownParser {
55
59
  private frozen;
56
60
  private generation;
57
61
  private cached;
62
+ private openFence;
58
63
  /** @param parse - Grammar shared with whatever renders the blocks, so boundaries agree. */
59
64
  constructor(parse: (text: string) => Root);
65
+ /** Parse one unclosed-fence content slice through the caller's grammar. */
66
+ private fenceValue;
67
+ /** Recognize the parsed tail's final unclosed fence and prepare its incremental content frontier. */
68
+ private openFenceState;
69
+ /** Extend a recognized unclosed fence without parsing its completed content prefix again. */
70
+ private updateOpenFence;
60
71
  /**
61
72
  * Fold the current accumulated text and return the frozen/tail split.
62
73
  * Idempotent for identical input (the previous result is returned as-is),
@@ -21,9 +21,14 @@ import type { PositionedBlock } from './incremental.ts';
21
21
  /** Copy-button labels forwarded to fence CodeBlocks (this package is cordis-free, so copy arrives via props). */
22
22
  export interface MarkdownCodeLabels {
23
23
  /** Copy-button idle label. */
24
- copyLabel?: string | undefined;
24
+ copyLabel: string;
25
25
  /** Copy-button label during the post-copy confirmation window. */
26
- copiedLabel?: string | undefined;
26
+ copiedLabel: string;
27
+ }
28
+ /** Localized chrome for a Markdown document. */
29
+ export interface MarkdownLabels {
30
+ code: MarkdownCodeLabels;
31
+ footnotes: string;
27
32
  }
28
33
  /** Link/image reference targets collected from a document (first definition per identifier wins, as in CommonMark). */
29
34
  export interface ReferenceTargets {
@@ -67,10 +72,10 @@ export interface MarkdownFileMentions {
67
72
  * numbering accumulated in document order while references render.
68
73
  */
69
74
  export interface MarkdownRenderContext {
70
- /** Streaming arm: fences render plain and TeX stays literal. */
75
+ /** Streaming arm: fences highlight incrementally as they grow; TeX (including ```math fences) stays literal until the settled pass. */
71
76
  readonly streaming: boolean;
72
77
  /** Localized fence copy-button labels. */
73
- readonly codeLabels: MarkdownCodeLabels | undefined;
78
+ readonly labels: MarkdownLabels;
74
79
  /** Inside a blockquote's children: tables there always fill the quote's width. */
75
80
  readonly inBlockquote?: boolean;
76
81
  /** Inline-code file mentions; absent wherever no opener vocabulary exists. */
@@ -0,0 +1,11 @@
1
+ import type { RefObject } from 'react';
2
+ /**
3
+ * Activate one supported code surface when it first intersects the viewport.
4
+ * Activation lasts for the component lifetime; browsers without
5
+ * IntersectionObserver activate immediately.
6
+ * @param target - Code surface whose plain rendering reserves its geometry.
7
+ * @param lang - Optional language hint.
8
+ * @returns Whether this component may build highlighted output.
9
+ */
10
+ export declare function useViewportHighlighting(target: RefObject<Element>, lang: string | undefined): boolean;
11
+ //# sourceMappingURL=useViewportHighlighting.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Compact relative-time bucketing shared by every surface that dates a
3
+ * session. Bucketing is here so two surfaces naming the same session agree;
4
+ * the words stay in each plugin's own dictionary, per locale-owned copy.
5
+ *
6
+ * @module @deepseek-ai/dsh-client-ui-primitives/relative-time
7
+ */
8
+ /** Relative-time bucket of a dated row's trailing label. */
9
+ export type RelativeTimeUnit = 'now' | 'minutes' | 'hours' | 'days' | 'months' | 'years';
10
+ /** Structured relative time: the bucket plus its magnitude (0 for 'now'). */
11
+ export interface RelativeTime {
12
+ unit: RelativeTimeUnit;
13
+ n: number;
14
+ }
15
+ /**
16
+ * Compact relative time, as a structured bucket the renderer localizes
17
+ * ("now"/"5min"/"3h"/"2d"/"4mo"/"1y" in en).
18
+ * @param at - epoch ms of the dated moment.
19
+ * @param now - current epoch ms (injected for pure rendering).
20
+ * @returns the row's trailing time bucket and magnitude.
21
+ */
22
+ export declare function relativeTime(at: number, now: number): RelativeTime;
23
+ //# sourceMappingURL=relative-time.d.ts.map
@@ -3,10 +3,10 @@
3
3
  *
4
4
  * A portaled panel is positioned from its anchor's viewport rect, which stops
5
5
  * being true the moment anything scrolls or the window resizes. This owns that
6
- * one concern: measure the anchor, offset the panel below it, clamp the result
7
- * inside the viewport, and re-run on scroll (capture phase, so scrollers nested
8
- * inside the page are caught too), on resize, and on the panel's own size
9
- * changes while the element is open.
6
+ * one concern: measure the anchor, offset the panel below or above it, clamp
7
+ * the result inside the viewport, and re-run on scroll (capture phase, so
8
+ * scrollers nested inside the page are caught too), on resize, and on the
9
+ * panel's own size changes while the element is open.
10
10
  * @module @deepseek-ai/dsh-client-ui-primitives/useAnchoredPosition
11
11
  */
12
12
  import { type CSSProperties, type RefObject } from 'react';
@@ -18,14 +18,16 @@ export interface AnchoredPositionOptions {
18
18
  anchorRef: RefObject<HTMLElement | null>;
19
19
  /** The floating element, measured so the clamp uses real dimensions. */
20
20
  panelRef: RefObject<HTMLElement | null>;
21
- /** Distance kept between the anchor's bottom edge and the panel's top. */
21
+ /** Which anchor edge the panel hangs from: below it (`bottom`, the default) or above it (`top`). */
22
+ side?: 'top' | 'bottom';
23
+ /** Distance kept between the anchor edge named by `side` and the panel. */
22
24
  gap: number;
23
25
  /** Distance kept between the panel and each viewport edge. */
24
26
  margin: number;
25
27
  }
26
28
  /**
27
29
  * Track an anchor and return the panel's fixed coordinates.
28
- * @param options - the open state, the two refs, and the gap/margin distances.
30
+ * @param options - the open state, the two refs, the placement side, and the gap/margin distances.
29
31
  * @returns `left`/`top` for the panel, or `null` before the first measurement.
30
32
  */
31
33
  export declare function useAnchoredPosition(options: AnchoredPositionOptions): CSSProperties | null;
@@ -4,6 +4,8 @@ import type { RefObject } from 'react';
4
4
  * @param root - element containing both the trigger and the open surface.
5
5
  * @param open - whether the surface is showing; false detaches the listener.
6
6
  * @param setOpen - state setter invoked with false on an outside pointerdown.
7
+ * @param portal - surface portaled outside the root (a `document.body` dialog)
8
+ * that also counts as inside; omit when the root contains the whole popover.
7
9
  */
8
- export declare function useDismissOnOutsidePointer(root: RefObject<HTMLElement | null>, open: boolean, setOpen: (open: boolean) => void): void;
10
+ export declare function useDismissOnOutsidePointer(root: RefObject<HTMLElement | null>, open: boolean, setOpen: (open: boolean) => void, portal?: RefObject<HTMLElement | null>): void;
9
11
  //# sourceMappingURL=useDismissOnOutsidePointer.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Display projection of reference forms in sent user text (bubble and queue
3
+ * rows). The logged model text remains the single truth; this is presentation
4
+ * only, and every part renders inline so a single-line message never breaks
5
+ * across lines. Three decoration sources, by precedence: the wire session form
6
+ * `@[label](dsh-session:...)` folds to its label; exact session labels
7
+ * supplied by an adjacent recall decorate their bare `@label` mention; and
8
+ * plain `/name` / `@name` word-boundary tokens decorate by shape alone (sent
9
+ * tokens were validated at compose time).
10
+ */
11
+ import type { ReactNode } from 'react';
12
+ /**
13
+ * Split one sent text into inline plain runs and reference chips.
14
+ * @param text - the logged model text of the message or queue row.
15
+ * @param sessionLabels - exact session mention labels associated by an adjacent recall.
16
+ * @returns inline nodes covering the whole text.
17
+ */
18
+ export declare function projectUserText(text: string, sessionLabels: readonly string[]): ReactNode;
19
+ //# sourceMappingURL=user-text.d.ts.map
@@ -0,0 +1,35 @@
1
+ /* Inline projection of sent user text: plain runs and reference chips flow in
2
+ the consumer's own line box (bubble or queue row); nothing here is block.
3
+ White-space policy stays with the consumer: the bubble wraps with pre-wrap,
4
+ the queue preview keeps its single nowrap/ellipsis line. */
5
+
6
+ .plainRun {
7
+ font-size: inherit;
8
+ line-height: inherit;
9
+ }
10
+
11
+ .refChip {
12
+ /* Plain inline, NOT inline-flex: a flex container takes its baseline from
13
+ its first flex item — the icon svg, which has no text baseline — so the
14
+ chip's label rode ~3px above the surrounding text in both surfaces. An
15
+ inline chip shares the consumer's baseline by construction. */
16
+ display: inline;
17
+ margin: 0 2px;
18
+ color: var(--dsw-alias-state-business-primary);
19
+ font-weight: 500;
20
+ white-space: nowrap;
21
+ }
22
+
23
+ /* Inline reference glyphs (always ReferenceIcon svgs) ride the consumer's own
24
+ font: 1em keeps the glyph at the text's size in the bubble (14px + user
25
+ setting), the queue preview's fixed 13px line, and any future consumer — a
26
+ px+delta size followed only the bubble axis and left the glyph oversized in
27
+ the queue row. */
28
+ .refIcon {
29
+ width: 1em;
30
+ height: 1em;
31
+ margin-right: 4px;
32
+ /* Optical centering against the text: drops the glyph below the baseline by
33
+ a font-relative amount, so it holds across the font axis. */
34
+ vertical-align: -0.125em;
35
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-client-ui-primitives",
3
3
  "description": "Pure React atoms for the dsh web UI: controls, icons, markdown, and JSON inspectors (zero cordis)",
4
- "version": "0.1.1-rc.2",
4
+ "version": "0.1.2-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -51,8 +51,8 @@
51
51
  "devDependencies": {
52
52
  "@types/react": "~18.3.1",
53
53
  "@types/react-dom": "~18.3.0",
54
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
55
- "@deepseek-ai/cordis": "^4.0.1"
54
+ "@deepseek-ai/dsh-invariants": "^0.1.2-alpha.3",
55
+ "@deepseek-ai/cordis": "^4.0.2"
56
56
  },
57
57
  "files": [
58
58
  "lib/index.js",
@@ -61,7 +61,6 @@
61
61
  "lib/types/**/*.d.ts"
62
62
  ],
63
63
  "peerDependencies": {
64
- "@deepseek-ai/cordis": "^4.0.1",
65
- "@deepseek-ai/dsh-invariants": "^0.1.1-rc.2"
64
+ "@deepseek-ai/cordis": "^4.0.2"
66
65
  }
67
66
  }
@@ -1,13 +0,0 @@
1
- .banner {
2
- position: fixed;
3
- top: 0;
4
- left: 0;
5
- right: 0;
6
- z-index: 100;
7
- padding: 4px 12px;
8
- text-align: center;
9
- font-size: 12px;
10
- line-height: 18px;
11
- background: var(--dsw-alias-state-error-primary);
12
- color: var(--dsw-alias-label-primary-foreground);
13
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Render the reconnecting banner.
3
- * @param props.reconnecting - true while the connection is in backoff/retry.
4
- * @param props.label - banner text; the owner passes localized copy (this
5
- * package is cordis-free, so copy arrives via props).
6
- * @returns the banner, or null when connected.
7
- */
8
- export declare function ConnectionBanner({ reconnecting, label }: {
9
- reconnecting: boolean;
10
- label?: string | undefined;
11
- }): import("react").JSX.Element | null;
12
- //# sourceMappingURL=ConnectionBanner.d.ts.map