@huaweidevtools/aishell-dsh-brand 0.0.1-beta.2 → 0.0.1-beta.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.
@@ -7,14 +7,42 @@
7
7
  */
8
8
  import type { SidebarBrandMarkOwnerProps } from '@deepseek-ai/dsh-client-ui-sidebar/client';
9
9
  import type { HeroBrandMarkOwnerProps } from '@deepseek-ai/dsh-client-ui-conversation/client';
10
+ /**
11
+ * A disposer undoing one module-level installation.
12
+ *
13
+ * The three helpers below all reach outside React — a `<style>` tag, event
14
+ * listeners, inserted nodes — so none of them is unwound by unmounting a
15
+ * component. They are installed through `ctx.effect` in `index.ts`, which runs
16
+ * the returned disposer on plugin unload. That matters beyond tidiness: the
17
+ * client loader tears a module down with `tearDownEntryFiber` (fiber effects)
18
+ * and `removeOwnedStyles` (only `style[data-plugin]` tags), so a bare
19
+ * `MutationObserver` on `document.body` is reachable by neither and would
20
+ * survive every HMR reload, accumulating one per reload.
21
+ */
22
+ export type BrandDisposer = () => void;
23
+ /** Inject the hero headline patch CSS; the disposer removes the tag. */
24
+ export declare function installHeroPatchCss(target: Document): BrandDisposer;
25
+ /**
26
+ * JS hover bridge: CSS ~ only matches forward siblings, so hovering the
27
+ * title text cannot trigger the logo's animation (the logo sits before the
28
+ * title in DOM order). This adds a mouseenter/mouseleave listener on the
29
+ * title span that toggles a class on the logo to trigger its bob animation.
30
+ *
31
+ * The disposer removes both listeners from every span it bridged, so a reload
32
+ * does not stack a second pair on the same element (the `data-` marker stops a
33
+ * *re-install* from adding them, but it also means a later disposer cannot
34
+ * find them by query alone — hence the bookkeeping below).
35
+ */
36
+ export declare function installHeroHoverBridge(target: Document): BrandDisposer;
10
37
  /**
11
38
  * Render the AI Shell mark with the presentation requested by its host surface.
12
39
  * Used in both the expanded sidebar brand row and the collapsed rail.
13
40
  */
14
41
  export declare function AishellBrandMark({ size }: SidebarBrandMarkOwnerProps): import("react").JSX.Element;
15
42
  /**
16
- * Render the AI Shell name with a version badge in the top-right corner,
17
- * matching DSH's native `.buildVersion` badge style.
43
+ * Render the AI Shell name in the sidebar brand row. The product name itself
44
+ * is drawn by the CSS module's `.name::before` (content: 'AI Shell'), so the
45
+ * markup is a single empty span.
18
46
  */
19
47
  export declare function AishellBrandName(): import("react").JSX.Element;
20
48
  /**
@@ -22,4 +50,13 @@ export declare function AishellBrandName(): import("react").JSX.Element;
22
50
  * className for surrounding mark geometry (e.g. centering).
23
51
  */
24
52
  export declare function AishellHeroBrandMark({ size, className }: HeroBrandMarkOwnerProps): import("react").JSX.Element;
53
+ /**
54
+ * Insert "Powered by deepseek-harness" below the hero headline, centered
55
+ * inside .stack. Uses a MutationObserver because the hero mounts/unmounts
56
+ * as the user navigates between sessions.
57
+ *
58
+ * The disposer stops the observer and removes every label this install added,
59
+ * so unloading the plugin leaves the hero exactly as it found it.
60
+ */
61
+ export declare function installPoweredBy(target: Document): BrandDisposer;
25
62
  //# sourceMappingURL=Brand.d.ts.map
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Example-prompt cards for the New-Session hero.
3
+ *
4
+ * ## Where this renders, and why it takes a portal to get there
5
+ *
6
+ * The entry occupies `conversation.input.dock`, the band between the hero
7
+ * workspace row and the input card. That is where the strip is MOUNTED — but
8
+ * not where it is DRAWN: the product wants the cards directly under the hero
9
+ * headline, and the workspace row sits between the two.
10
+ *
11
+ * Measured on the live page (1280×900):
12
+ *
13
+ * hero (headline) y=288 h=74
14
+ * hero workspace row y=374 h=28
15
+ * conversation.input.dock y=411 h=80
16
+ * the input card y=491
17
+ *
18
+ * The row cannot be crossed with a margin — it would be covered — and its DOM
19
+ * is a sibling subtree, unreachable from the dock. So the strip renders
20
+ * through `createPortal` into the hero's own content div, the empty `_body`
21
+ * the official shell leaves for exactly this. See `hero-content.ts` for how
22
+ * that mount is found and why it fails closed.
23
+ *
24
+ * The dock entry is still what supplies the slot props (`useSession`,
25
+ * `inputActions`, `t`), so the registration is unchanged; the portal only
26
+ * relocates the output. What the dock band itself renders is nothing.
27
+ *
28
+ * ## When it renders
29
+ *
30
+ * The dock band renders in BOTH the hero and the docked composer (it only
31
+ * needs a live input zone), so the gate is `session.blank` — the same
32
+ * condition the account chip next door uses. `blank` is true from the moment a
33
+ * New Session opens until the first turn is accepted: exactly the window the
34
+ * cards exist for. Once the first message is sent the strip renders nothing
35
+ * and the composer takes over.
36
+ *
37
+ * ## Why it does not look like the input card
38
+ *
39
+ * The first version read as a second input box, and the reason was measurable
40
+ * rather than a matter of taste. Both surfaces were built from the same two
41
+ * ingredients the shell reserves for a composer:
42
+ *
43
+ * - the same neutral one step apart — the card was `bg-module-platform`
44
+ * (`#353638` in dark) and the composer `specific-input-major` (`#2c2c2e`),
45
+ * both rungs of one ramp, so the two read as siblings;
46
+ * - the same box-shadow token. `--dsw-elevation-soft` expands to
47
+ * `var(--dsw-elevation-stroke), …`, and that stroke is a
48
+ * `0 0 0 .5px` ring — so "no border" had not removed the outline at all,
49
+ * it had only moved it into the shadow. Both surfaces carry a hairline.
50
+ *
51
+ * The fix is to use the shell's vocabulary for a CHOICE rather than for a
52
+ * FIELD. Every pickable surface in the product — the option list in the
53
+ * question card (`border-radius: var(--dsw-radius-md)`, `background: 0 0`),
54
+ * the command and trigger lists, the portalled menu — draws itself the same
55
+ * way: a transparent ground, a `radius-md` corner, and a fill that appears
56
+ * only on hover or selection. A field, by contrast, is an opaque well inset
57
+ * into the page.
58
+ *
59
+ * So a card here is:
60
+ *
61
+ * - TRANSLUCENT, not opaque and not empty. The shell explains this exact
62
+ * choice for `SegmentedControl`: a translucent fill "reads as a place
63
+ * rather than a second button". Opaque is what made the card a well;
64
+ * fully transparent is invisible on a white page, where the light theme
65
+ * would leave nothing but floating text. The tint is the shell's own
66
+ * interactive pair — `interactive-bg-hover` at rest, `interactive-bg-active`
67
+ * on hover — so hovering reads as the same material intensifying;
68
+ * - `radius-md` (12px), the choice radius, against the composer's 28px
69
+ * `radius-panel`;
70
+ * - no shadow at rest. The shell gives an elevated surface a shadow only
71
+ * when it FLOATS over other content (a menu, a hovercard, a modal); a
72
+ * card sitting inline in the page gets none, so neither does this.
73
+ *
74
+ * The icon tile keeps an accent tint — `Tag`'s info tone and the plan
75
+ * package's chip both paint `color-mix(… business-primary 10%, transparent)`
76
+ * — on a square with `radius-sm`, matching those chips' geometry.
77
+ *
78
+ * ## Auto-rotation
79
+ *
80
+ * The strip shows three of four cards, so the fourth would otherwise be
81
+ * invisible to anyone who does not touch the arrows. It advances one card
82
+ * every {@link AUTOPLAY_INTERVAL_MS}, pausing on hover, on keyboard focus, in
83
+ * a background tab, and under `prefers-reduced-motion`. The policy itself
84
+ * lives in `autoplay.ts`, kept pure so the list of reasons to stop is testable
85
+ * rather than buried in an effect.
86
+ *
87
+ * @module @huaweidevtools/aishell-dsh-brand/client/SuggestionCards
88
+ */
89
+ import { type ReactNode } from 'react';
90
+ import type { PropsRuntime, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
91
+ /** Full component props: the dock band's owner share + the session kit + `t`. */
92
+ export type SuggestionCardsProps = PropsRuntime<'conversation.input.dock'> & PropsLocale<'aishell-brand'>;
93
+ export declare function SuggestionCards(props: SuggestionCardsProps): ReactNode;
94
+ //# sourceMappingURL=SuggestionCards.d.ts.map
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The AI Shell mark as an inline source, shared by the runtime brand shadows.
3
+ *
4
+ * The slot occupants (Brand.tsx) draw the mark through a CSS module whose
5
+ * url() the build inlines, but the runtime shadows need the bytes as a JS
6
+ * string: the favicon shadow writes them into a link href, and the running
7
+ * status mark shadow writes them into injected patch CSS. This module is
8
+ * that one source, so the asset and the shadows cannot drift apart.
9
+ * tests/aishell-mark.spec.ts pins the constant against assets/aishell.svg.
10
+ *
11
+ * @module @huaweidevtools/aishell-dsh-brand/client/aishell-mark
12
+ */
13
+ /** The AI Shell mark, verbatim assets/aishell.svg (the spec pins this). */
14
+ export declare const AISHELL_MARK_SVG = "<svg viewBox=\"0 0 44 44\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"44.000000\" height=\"44.000000\" fill=\"none\">\n\t<defs>\n\t\t<linearGradient id=\"paint_linear_0\" x1=\"38.5\" x2=\"2.19999695\" y1=\"1.90734863e-06\" y2=\"39.5999908\" gradientUnits=\"userSpaceOnUse\">\n\t\t\t<stop stop-color=\"rgb(53,137,255)\" offset=\"0\" stop-opacity=\"1\" />\n\t\t\t<stop stop-color=\"rgb(169,38,255)\" offset=\"1\" stop-opacity=\"1\" />\n\t\t</linearGradient>\n\t</defs>\n\t<rect id=\"icon_AIShell\" width=\"44.000000\" height=\"44.000000\" x=\"0.000000\" y=\"0.000000\" fill=\"rgb(255,255,255)\" fill-opacity=\"0\" />\n\t<path id=\"\u5408\u5E76\" d=\"M37.4576 0.299738C37.3775 0.139877 37.2861 0.0428196 37.1832 0.00856393C37.0917 -0.0142732 37.006 0.00856394 36.9259 0.0770754C36.8459 0.145587 36.7887 0.219808 36.7545 0.299738C36.4572 1.86408 35.7369 3.21718 34.5937 4.35904C33.4618 5.48948 32.0956 6.22027 30.495 6.55141C30.3921 6.57424 30.3178 6.64275 30.272 6.75694C30.2377 6.87113 30.2435 6.97389 30.2892 7.06524C30.3464 7.15659 30.415 7.20226 30.495 7.20226C31.2078 7.35296 31.8779 7.58759 32.5055 7.90618L32.5055 7.9062L32.5056 7.90623C33.257 8.28771 33.9473 8.78955 34.5765 9.41176C35.7312 10.5422 36.4572 11.8896 36.7545 13.4539C36.8116 13.591 36.8974 13.6823 37.0117 13.728C37.1146 13.7622 37.2061 13.7565 37.2861 13.7109C37.3775 13.6538 37.4347 13.5681 37.4576 13.4539C37.7891 11.8896 38.5151 10.5422 39.6355 9.41176C40.7674 8.2699 42.1279 7.5334 43.717 7.20226C43.8199 7.17943 43.9 7.12233 43.9571 7.03099C44.0143 6.92822 44.0143 6.83116 43.9571 6.73981C43.9 6.63705 43.8199 6.57424 43.717 6.55141C42.1507 6.22027 40.7959 5.48948 39.6527 4.35904C38.5208 3.21718 37.7891 1.86408 37.4576 0.299738ZM42.625 16.4999L42.625 33C42.625 33.9493 42.4572 34.829 42.1216 35.6392C41.786 36.4495 41.2826 37.1902 40.6114 37.8614C39.9402 38.5326 39.1994 39.036 38.3892 39.3716C37.579 39.7072 36.6992 39.875 35.75 39.875L10.9999 39.875L8.24995 39.875C7.30073 39.875 6.42102 39.7072 5.61081 39.3716C4.80058 39.036 4.05985 38.5326 3.38863 37.8614C2.71742 37.1902 2.21402 36.4494 1.87841 35.6392C1.5428 34.829 1.375 33.9493 1.375 33L1.375 11C1.375 10.0508 1.5428 9.17106 1.8784 8.36083L1.87841 8.36082C2.21402 7.55059 2.71743 6.80987 3.38864 6.13866C4.05985 5.46745 4.80057 4.96404 5.61079 4.62843L5.6108 4.62843C6.42103 4.29282 7.30076 4.12502 8.25 4.12502L27.4999 4.125C28.2699 4.125 28.8749 4.73 28.8749 5.5C28.8749 6.27 28.2699 6.875 27.4999 6.875L8.25 6.87502C7.68045 6.87502 7.15261 6.9757 6.66648 7.17706C6.18034 7.37843 5.73591 7.68047 5.33318 8.0832C4.93046 8.48593 4.62841 8.93036 4.42705 9.41649C4.22568 9.90263 4.125 10.4305 4.125 11L4.125 33C4.125 33.5696 4.22568 34.0974 4.42705 34.5835C4.62841 35.0697 4.93045 35.5141 5.33317 35.9168C5.73588 36.3195 6.18028 36.6216 6.66639 36.8229C7.15256 37.0243 7.68042 37.125 8.24999 37.125L10.9999 37.125L35.75 37.125C36.3195 37.125 36.8474 37.0243 37.3335 36.823C37.8197 36.6216 38.2641 36.3196 38.6668 35.9168C39.0695 35.5141 39.3716 35.0697 39.573 34.5835C39.7743 34.0974 39.875 33.5696 39.875 33L39.875 16.4999C39.875 15.7299 40.48 15.1249 41.25 15.1249C42.02 15.1249 42.625 15.7299 42.625 16.4999ZM10.0277 12.7777C10.5722 12.2333 11.4278 12.2333 11.9723 12.7777L20.2222 21.0277C20.4907 21.2962 20.6249 21.6203 20.6249 22C20.6249 22.3797 20.4907 22.7037 20.2222 22.9722L11.9722 31.2223C11.4277 31.7668 10.5721 31.7668 10.0277 31.2223C9.48318 30.6779 9.48317 29.8223 10.0276 29.2778L17.3054 22L10.0277 14.7223C9.48325 14.1778 9.48326 13.3222 10.0277 12.7777ZM20.625 28.875C20.625 28.105 21.23 27.5 22 27.5L33 27.5C33.77 27.5 34.375 28.105 34.375 28.875C34.375 29.645 33.77 30.25 33 30.25L22 30.25C21.23 30.25 20.625 29.645 20.625 28.875Z\" fill=\"rgb(0,0,0)\" fill-opacity=\"0\" fill-rule=\"evenodd\" />\n\t<path id=\"\u5408\u5E76\" d=\"M37.4576 0.299738C37.3775 0.139877 37.2861 0.0428196 37.1832 0.00856393C37.0917 -0.0142732 37.006 0.00856394 36.9259 0.0770754C36.8459 0.145587 36.7887 0.219808 36.7545 0.299738C36.4572 1.86408 35.7369 3.21718 34.5937 4.35904C33.4618 5.48948 32.0956 6.22027 30.495 6.55141C30.3921 6.57424 30.3178 6.64275 30.272 6.75694C30.2377 6.87113 30.2435 6.97389 30.2892 7.06524C30.3464 7.15659 30.415 7.20226 30.495 7.20226C31.2078 7.35296 31.8779 7.58759 32.5055 7.90618L32.5055 7.9062L32.5056 7.90623C33.257 8.28771 33.9473 8.78955 34.5765 9.41176C35.7312 10.5422 36.4572 11.8896 36.7545 13.4539C36.8116 13.591 36.8974 13.6823 37.0117 13.728C37.1146 13.7622 37.2061 13.7565 37.2861 13.7109C37.3775 13.6538 37.4347 13.5681 37.4576 13.4539C37.7891 11.8896 38.5151 10.5422 39.6355 9.41176C40.7674 8.2699 42.1279 7.5334 43.717 7.20226C43.8199 7.17943 43.9 7.12233 43.9571 7.03099C44.0143 6.92822 44.0143 6.83116 43.9571 6.73981C43.9 6.63705 43.8199 6.57424 43.717 6.55141C42.1507 6.22027 40.7959 5.48948 39.6527 4.35904C38.5208 3.21718 37.7891 1.86408 37.4576 0.299738ZM42.625 16.4999L42.625 33C42.625 33.9493 42.4572 34.829 42.1216 35.6392C41.786 36.4495 41.2826 37.1902 40.6114 37.8614C39.9402 38.5326 39.1994 39.036 38.3892 39.3716C37.579 39.7072 36.6992 39.875 35.75 39.875L10.9999 39.875L8.24995 39.875C7.30073 39.875 6.42102 39.7072 5.61081 39.3716C4.80058 39.036 4.05985 38.5326 3.38863 37.8614C2.71742 37.1902 2.21402 36.4494 1.87841 35.6392C1.5428 34.829 1.375 33.9493 1.375 33L1.375 11C1.375 10.0508 1.5428 9.17106 1.8784 8.36083L1.87841 8.36082C2.21402 7.55059 2.71743 6.80987 3.38864 6.13866C4.05985 5.46745 4.80057 4.96404 5.61079 4.62843L5.6108 4.62843C6.42103 4.29282 7.30076 4.12502 8.25 4.12502L27.4999 4.125C28.2699 4.125 28.8749 4.73 28.8749 5.5C28.8749 6.27 28.2699 6.875 27.4999 6.875L8.25 6.87502C7.68045 6.87502 7.15261 6.9757 6.66648 7.17706C6.18034 7.37843 5.73591 7.68047 5.33318 8.0832C4.93046 8.48593 4.62841 8.93036 4.42705 9.41649C4.22568 9.90263 4.125 10.4305 4.125 11L4.125 33C4.125 33.5696 4.22568 34.0974 4.42705 34.5835C4.62841 35.0697 4.93045 35.5141 5.33317 35.9168C5.73588 36.3195 6.18028 36.6216 6.66639 36.8229C7.15256 37.0243 7.68042 37.125 8.24999 37.125L10.9999 37.125L35.75 37.125C36.3195 37.125 36.8474 37.0243 37.3335 36.823C37.8197 36.6216 38.2641 36.3196 38.6668 35.9168C39.0695 35.5141 39.3716 35.0697 39.573 34.5835C39.7743 34.0974 39.875 33.5696 39.875 33L39.875 16.4999C39.875 15.7299 40.48 15.1249 41.25 15.1249C42.02 15.1249 42.625 15.7299 42.625 16.4999ZM10.0277 12.7777C10.5722 12.2333 11.4278 12.2333 11.9723 12.7777L20.2222 21.0277C20.4907 21.2962 20.6249 21.6203 20.6249 22C20.6249 22.3797 20.4907 22.7037 20.2222 22.9722L11.9722 31.2223C11.4277 31.7668 10.5721 31.7668 10.0277 31.2223C9.48318 30.6779 9.48317 29.8223 10.0276 29.2778L17.3054 22L10.0277 14.7223C9.48325 14.1778 9.48326 13.3222 10.0277 12.7777ZM20.625 28.875C20.625 28.105 21.23 27.5 22 27.5L33 27.5C33.77 27.5 34.375 28.105 34.375 28.875C34.375 29.645 33.77 30.25 33 30.25L22 30.25C21.23 30.25 20.625 29.645 20.625 28.875Z\" fill=\"url(#paint_linear_0)\" fill-rule=\"evenodd\" />\n</svg>";
15
+ /**
16
+ * The mark as a self-contained data URI.
17
+ *
18
+ * URL-encoded rather than base64 (shorter, and the gradient reads on both
19
+ * light and dark themes, which is what lets one URI serve every surface).
20
+ */
21
+ export declare const AISHELL_MARK_URI: string;
22
+ //# sourceMappingURL=aishell-mark.d.ts.map
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Auto-rotation policy for the example-prompt strip.
3
+ *
4
+ * Kept apart from the component and free of DOM, for the same reason
5
+ * `carousel.ts` is: the DECISION is what needs pinning down, and a decision
6
+ * buried in an effect can only be checked by watching a browser.
7
+ *
8
+ * ## Why the strip advances on its own
9
+ *
10
+ * The strip shows three of four cards, so a card is always off screen and a
11
+ * reader who never touches the arrows never learns it exists. Rotation is what
12
+ * makes the set self-evident. The cost is that content moves without being
13
+ * asked to, which is why every reason to stop is collected here rather than
14
+ * decided piecemeal at the call site.
15
+ *
16
+ * ## Every reason to stop
17
+ *
18
+ * - `engaged` — the pointer is over the strip or focus is inside it. Someone
19
+ * reaching for a card must not have it slide out from under the cursor;
20
+ * this is the pause the feature was asked for, and `focus` is the half of
21
+ * it that covers a reader who never uses a pointer.
22
+ * - `reducedMotion` — the reader asked the platform for less movement. An
23
+ * element that moves by itself for more than five seconds is exactly what
24
+ * `prefers-reduced-motion` exists to suppress, and a hover pause cannot help
25
+ * the reader who cannot hover. Honouring it is what keeps the feature from
26
+ * being motion no one consented to.
27
+ * - `visible` — the tab is hidden. A background tab still fires timers, so
28
+ * without this the strip would churn through slides nobody can see and land
29
+ * somewhere arbitrary on return.
30
+ * - `ready` — the ring has not been measured yet. Advancing before the step
31
+ * is known would move the track by a wrong distance; see the measurement
32
+ * note in `SuggestionCards.tsx`.
33
+ * - `ring` — three cards or fewer all fit, so there is nowhere to advance to.
34
+ * A strip with nothing to scroll must not appear to scroll.
35
+ *
36
+ * @module @huaweidevtools/aishell-dsh-brand/client/autoplay
37
+ */
38
+ /**
39
+ * How long each card is shown before the strip advances.
40
+ *
41
+ * Fixed rather than configurable: this is a hint at what the product can do,
42
+ * not a reading surface, and a setting nobody asked for is a setting nobody
43
+ * finds.
44
+ */
45
+ export declare const AUTOPLAY_INTERVAL_MS = 1500;
46
+ /** Everything that can hold the strip still. */
47
+ export interface AutoplayInputs {
48
+ /** More cards than fit, so there is somewhere to advance to. */
49
+ readonly ring: boolean;
50
+ /** The ring has been measured and the track can be moved a known distance. */
51
+ readonly ready: boolean;
52
+ /** The pointer is over the strip, or focus is inside it. */
53
+ readonly engaged: boolean;
54
+ /** The reader asked for reduced motion. */
55
+ readonly reducedMotion: boolean;
56
+ /** The tab is on screen. */
57
+ readonly visible: boolean;
58
+ }
59
+ /**
60
+ * Whether the strip should be advancing on its own right now.
61
+ *
62
+ * Expressed as one predicate rather than a chain of early returns inside the
63
+ * timer, so that adding a reason to stop is a change to this list and its
64
+ * test — not a change to the effect that schedules the ticks.
65
+ */
66
+ export declare function autoplayActive(inputs: AutoplayInputs): boolean;
67
+ //# sourceMappingURL=autoplay.d.ts.map
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Carousel position maths, kept pure so it can be tested without a DOM.
3
+ *
4
+ * The strip is a RING, not a scroll range: the cards form a circle, and an
5
+ * arrow steps one card around it forever. Clicking next from the last
6
+ * position lands on the first, and clicking prev from the first lands on the
7
+ * last — and crucially, every intermediate position exists. A four-card ring
8
+ * showing three at a time has FOUR positions, not two:
9
+ *
10
+ * cards 1 2 3 4, three visible
11
+ * pos 0 → [1 2 3]
12
+ * pos 1 → [2 3 4]
13
+ * pos 2 → [3 4 1] ← the wrap is visible, not a jump back
14
+ * pos 3 → [4 1 2]
15
+ * pos 4 → [1 2 3] same as pos 0
16
+ *
17
+ * A plain scroll container cannot express that: its range is only
18
+ * `content - viewport` (one step, for four cards), and positions 2 and 3 have
19
+ * no place to live. So the card list is rendered {@link COPIES} times over
20
+ * and the window travels through the middle copy, where both neighbours
21
+ * always have content.
22
+ *
23
+ * ## Why the copies, and how the seam stays invisible
24
+ *
25
+ * With the list rendered three times, a window at physical position `p` shows
26
+ * `cards[p .. p+VISIBLE-1]`, and position `2n` shows exactly what position `n`
27
+ * shows. That equivalence is the seam: once a slide settles at `2n`, the
28
+ * track can be placed at `n` in the same frame with transitions off, and
29
+ * nothing on screen changes. The same holds at the other end, where `n-1` and
30
+ * `2n-1` are the same window.
31
+ *
32
+ * The rewind happens only AFTER the slide finishes — never before. Jumping
33
+ * first would put the destination on screen instantly and then animate away
34
+ * from it, so a click at the seam would appear to skip a card. This is the
35
+ * one law in this module that a reader is likely to get backwards.
36
+ *
37
+ * @module @huaweidevtools/aishell-dsh-brand/client/carousel
38
+ */
39
+ /**
40
+ * How many times the card list is rendered.
41
+ *
42
+ * Three is the minimum that works. The window lives in the middle copy, so it
43
+ * needs one full copy behind it (to show the cards before the start) and one
44
+ * ahead (to show the cards past the end while a slide is finishing at the
45
+ * seam, where the position may briefly reach `2n` and read up to
46
+ * `2n + VISIBLE - 1`).
47
+ */
48
+ export declare const COPIES = 3;
49
+ /** How many cards to render for a ring of `n`. */
50
+ export declare function ringLength(n: number): number;
51
+ /**
52
+ * The physical position the ring starts at — the first card of the middle
53
+ * copy. Starting here rather than at 0 is what makes the very first prev
54
+ * click land on a real card instead of an empty track.
55
+ */
56
+ export declare function ringStart(n: number): number;
57
+ /**
58
+ * Where a settled position should be silently rewound to, or `null` when it
59
+ * is already inside the working band.
60
+ *
61
+ * The band is `[n, 2n-1]`. A slide may finish one past either end — `2n`
62
+ * going forward, `n-1` going backward — and both have an exact twin inside
63
+ * the band. See the module note for why this runs after the slide, not
64
+ * before.
65
+ */
66
+ export declare function rewindTo(pos: number, n: number): number | null;
67
+ /** One arrow press, resolved into the physical positions involved. */
68
+ export interface RingStep {
69
+ /**
70
+ * Where the track must be placed with transitions OFF before the slide.
71
+ * Equal to the incoming position when no rewind is due; when it differs,
72
+ * the jump is invisible (both positions show the same window).
73
+ */
74
+ readonly from: number;
75
+ /**
76
+ * Where the slide ends. May sit one past the band's end, in which case the
77
+ * next {@link rewindTo} brings it back.
78
+ */
79
+ readonly to: number;
80
+ }
81
+ /**
82
+ * Resolve one arrow press on a ring.
83
+ *
84
+ * Any rewind still owed from the previous press is settled first, so a person
85
+ * clicking faster than the slide animation cannot walk the window off the
86
+ * rendered copies — the band always absorbs one more step.
87
+ */
88
+ export declare function ringStep(pos: number, delta: number, n: number): RingStep;
89
+ /**
90
+ * One arrow press on a plain strip (fewer cards than fit): step by one and
91
+ * stay inside the scrollable range.
92
+ *
93
+ * @param maxPos - the last meaningful position, `0` when nothing scrolls.
94
+ */
95
+ export declare function clampStep(pos: number, delta: number, maxPos: number): number;
96
+ //# sourceMappingURL=carousel.d.ts.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Runtime favicon and manifest shadowing for the branded distribution.
3
+ *
4
+ * The browser tab icon comes from the dsh-web-frontend dist's
5
+ * favicon.svg / favicon-dark.svg (a DeepSeek whale, one fixed fill per
6
+ * theme, selected by the index's `media="(prefers-color-scheme: …)"`
7
+ * links), and that dist tree is covered by the desktop-runtime digest —
8
+ * the file-level swap cannot reach it. The plugin side therefore shadows
9
+ * the links at runtime: every `link[rel~="icon"]` in the head has its
10
+ * href rewritten to the AI Shell mark data URI. The mark's gradient
11
+ * reads on both light and dark themes, so the media attribute is dropped
12
+ * and one icon serves both.
13
+ *
14
+ * The web manifest's display names ("DeepSeek Harness" / "DSH") are
15
+ * shadowed the same way when they can be: the manifest is fetched, the
16
+ * name/short_name fields rewritten to "AI Shell", and the link pointed
17
+ * at a Blob URL of the result. A manifest that cannot be fetched or
18
+ * parsed is left exactly as found — a broken install prompt would cost
19
+ * more than the name is worth.
20
+ *
21
+ * A MutationObserver watches the head, so a re-inserted icon or manifest
22
+ * link (HMR, later injections) is re-shadowed as it appears.
23
+ *
24
+ * Shadowing is a default, not a force (plan §2.2): install() and the
25
+ * disposer it returns are symmetric, so disabling the plugin restores
26
+ * the official links exactly.
27
+ *
28
+ * @module @huaweidevtools/aishell-dsh-brand/client/favicon-shadow
29
+ */
30
+ /** The disposer restores the official icon and manifest links; a no-op install returns this. */
31
+ export type FaviconShadowDisposer = () => void;
32
+ /**
33
+ * Rewrite a manifest document's display names in place.
34
+ *
35
+ * Only the two fields the dist actually ships are touched, and only when
36
+ * they carry their official values — anything else in the document (icons,
37
+ * start_url, scope, display) passes through untouched, and a manifest the
38
+ * base customized keeps its customization.
39
+ */
40
+ export declare function rebrandManifest(manifest: Record<string, unknown>): Record<string, unknown>;
41
+ /** The href every icon link is shadowed with (exposed for the spec). */
42
+ export declare const BRANDED_ICON_HREF: string;
43
+ /**
44
+ * Install the favicon shadow on a document.
45
+ *
46
+ * Rewrites every current icon link's href to the AI Shell mark (dropping
47
+ * the media attribute so the one gradient serves both themes), shadows the
48
+ * manifest names when the manifest is reachable, and observes the head so
49
+ * re-inserted links are covered too. Returns the disposer: every touched
50
+ * link is restored to its original href and media, the manifest link to
51
+ * its original href, and the Blob URL is revoked, so an unload leaves the
52
+ * document exactly as it was found.
53
+ */
54
+ export declare function installFaviconShadow(target: Document): FaviconShadowDisposer;
55
+ //# sourceMappingURL=favicon-shadow.d.ts.map
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The mount point the suggestion strip renders into, inside the hero.
3
+ *
4
+ * ## Why a portal, and not CSS placement
5
+ *
6
+ * The strip is a `conversation.input.dock` entry, and that band renders BELOW
7
+ * the hero workspace row in the composer stack. Measured on the live page
8
+ * (1280×900, DSH 0.2.x):
9
+ *
10
+ * hero (headline) y=288 h=74
11
+ * hero workspace row y=374 h=28
12
+ * conversation.input.dock y=411 h=80
13
+ * the input card y=491
14
+ *
15
+ * The product wants the cards directly under the headline, and the workspace
16
+ * row sits between. A negative margin cannot cross it — the row would be
17
+ * covered — and the row's own subtree is not reachable from the dock, since
18
+ * the two are separate children of the composer stack.
19
+ *
20
+ * The hero, however, already renders an empty content div for exactly this
21
+ * purpose. Its stack is: headline → (the "Powered by" label) → `_body`, where
22
+ * `_body` is `flex-direction: column` and left empty by the official shell.
23
+ * That is the hero's content area, and putting the cards there is using the
24
+ * slot as intended rather than fighting the layout.
25
+ *
26
+ * ## How the mount is found
27
+ *
28
+ * By the hero's own class suffix, from the headline outward — `[class*="_body"]`
29
+ * alone is ambiguous (the conversation root has a `_body` too, and the live
30
+ * page has two). So the search starts at a `[class*="_headline"]`, steps to its
31
+ * parent (the hero stack), and takes that stack's own `_body` child. The
32
+ * headline is unique on the page and is what makes the walk unambiguous.
33
+ *
34
+ * Children are walked directly rather than with `:scope >`. The two are
35
+ * equivalent here, and the direct walk keeps these helpers working against the
36
+ * hand-rolled DOM stub the specs use (which has no selector engine) instead of
37
+ * leaving the module's real logic untested.
38
+ *
39
+ * The classes are CSS-module hashes (`pXSMma_body`), hence the suffix match.
40
+ * A base bump that renames them fails closed: {@link findHeroContent} returns
41
+ * null, the portal renders nothing, and the arrows and cards simply do not
42
+ * appear — the strip is never left in the wrong parent.
43
+ *
44
+ * @module @huaweidevtools/aishell-dsh-brand/client/hero-content
45
+ */
46
+ /**
47
+ * The hero's content area, or `null` when the hero is not on screen.
48
+ *
49
+ * Returns null rather than throwing: a New Session that has not painted its
50
+ * hero yet is a normal state (and the strip retries on the next frame), not an
51
+ * error.
52
+ */
53
+ export declare function findHeroContent(target: Document): HTMLElement | null;
54
+ /**
55
+ * A container for the strip inside the hero content area.
56
+ *
57
+ * The hero body is `display: block` and zero-height, so the strip cannot rely
58
+ * on it for layout and must not inherit that block context. The wrapper is
59
+ * created here rather than rendered by React into the official div, because
60
+ * React does not own that node — it owns only what the portal puts inside.
61
+ */
62
+ export declare function ensureHeroMount(content: HTMLElement): HTMLElement;
63
+ /**
64
+ * Remove the mount this module created, if it is still there.
65
+ *
66
+ * Only the mount is removed — never the host div, which belongs to the shell
67
+ * and is reused on the next New Session.
68
+ */
69
+ export declare function removeHeroMount(content: HTMLElement): void;
70
+ //# sourceMappingURL=hero-content.d.ts.map
@@ -8,7 +8,7 @@
8
8
  * @module @huaweidevtools/aishell-dsh-brand/client
9
9
  */
10
10
  import type { Context as ClientContext } from '@deepseek-ai/cordis';
11
- /** Required service: the UI slot registry. */
11
+ /** Required services: the UI slot registry and the locale registry. */
12
12
  export declare const inject: string[];
13
13
  export declare function apply(ctx: ClientContext): void;
14
14
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,66 @@
1
+ /**
2
+ * aishell brand locale dictionaries.
3
+ *
4
+ * The brand plugin owns its own namespace rather than reusing `aishell`
5
+ * (which `aishell-credentials` registers): a namespace's texts have exactly
6
+ * one owner, and a duplicate `(ns, locale)` registration throws.
7
+ *
8
+ * ## Why content lives in a dictionary
9
+ *
10
+ * The suggestion cards are the only strings in this plugin a person reads,
11
+ * and they are CONTENT rather than interface chrome — a card title, its
12
+ * one-line gloss, and the prompt a click inserts. They still belong here:
13
+ * a card that says「云资源查询」to an English reader is worse than no card,
14
+ * and the prompt it inserts would start an English conversation in Chinese.
15
+ *
16
+ * Chinese is the key-set source of truth; English is complete against it and
17
+ * is also the terminal fallback, so every locale other than `zh` reads
18
+ * English. That is the framework's own chain (`LOCALE_IDS = ["zh", "en"]`,
19
+ * with English carrying no fallback) — there is no third dictionary to write.
20
+ *
21
+ * @module @huaweidevtools/aishell-dsh-brand/client/locales
22
+ */
23
+ /** Dictionary namespace owned by this plugin. */
24
+ export declare const NS: "aishell-brand";
25
+ /** Simplified Chinese dictionary (the key-set source of truth). */
26
+ export declare const zh: {
27
+ readonly 'suggestion.previous': "上一个示例";
28
+ readonly 'suggestion.next': "下一个示例";
29
+ readonly 'suggestion.inventory.title': "云资源查询";
30
+ readonly 'suggestion.inventory.gloss': "列出当前账号下的云资源";
31
+ readonly 'suggestion.inventory.prompt': "请列出我的华为云资源清单,按服务分类(计算、存储、网络、数据库等),标出每类资源的数量和所在区域。";
32
+ readonly 'suggestion.deploy.title': "部署方案推荐";
33
+ readonly 'suggestion.deploy.gloss': "按访问量和预算给出可落地的部署路径";
34
+ readonly 'suggestion.deploy.prompt': "我要部署一个轻量级 Web 应用,请对比几种可行的部署方案,说明各自的适用规模、月度成本量级和运维复杂度,并给出你的推荐及理由。";
35
+ readonly 'suggestion.enterprise.title': "企业上云";
36
+ readonly 'suggestion.enterprise.gloss': "多账号、网络分区与权限边界的整体设计";
37
+ readonly 'suggestion.enterprise.prompt': "请为我规划一套云上 Landing Zone 架构,覆盖账号组织结构、网络分区、权限边界与审计要求,并说明各部分的设计取舍。";
38
+ readonly 'suggestion.billing.title': "账单分析";
39
+ readonly 'suggestion.billing.gloss': "分析上个月的云资源支出";
40
+ readonly 'suggestion.billing.prompt': "请分析我上个月的云资源支出构成,找出占比最高的几项服务,指出其中可以优化或降配的部分,并估算调整后能节省多少。";
41
+ };
42
+ /** English dictionary. Complete against {@link zh}; also the terminal fallback. */
43
+ export declare const en: {
44
+ readonly 'suggestion.previous': "Previous example";
45
+ readonly 'suggestion.next': "Next example";
46
+ readonly 'suggestion.inventory.title': "Cloud inventory";
47
+ readonly 'suggestion.inventory.gloss': "List the resources in this account";
48
+ readonly 'suggestion.inventory.prompt': string;
49
+ readonly 'suggestion.deploy.title': "Deployment options";
50
+ readonly 'suggestion.deploy.gloss': "A workable path for your traffic and budget";
51
+ readonly 'suggestion.deploy.prompt': string;
52
+ readonly 'suggestion.enterprise.title': "Landing zone";
53
+ readonly 'suggestion.enterprise.gloss': "Multi-account structure, network and permission boundaries";
54
+ readonly 'suggestion.enterprise.prompt': string;
55
+ readonly 'suggestion.billing.title': "Billing analysis";
56
+ readonly 'suggestion.billing.gloss': "Break down last month's cloud spend";
57
+ readonly 'suggestion.billing.prompt': string;
58
+ };
59
+ /** Every key, derived from the zh dictionary (the source of truth). */
60
+ export type AishellBrandLocaleKey = keyof typeof zh;
61
+ declare module '@deepseek-ai/dsh-client-ui-slots' {
62
+ interface LocaleNamespaceMap {
63
+ 'aishell-brand': AishellBrandLocaleKey;
64
+ }
65
+ }
66
+ //# sourceMappingURL=locales.d.ts.map
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Running-status whale-tail shadow for the branded distribution.
3
+ *
4
+ * ui-chat's RunningWhaleTail renders the DeepSeek whale: an APNG
5
+ * (running-whale@2x.png) masked over currentColor inside a 14px
6
+ * runningIcon box, with a static stroke SVG fallback for reduced motion.
7
+ * All of it is digest-protected in the dsh tree, and there is no slot for
8
+ * it — the row lives inside ui-chat's ChatView, which a plugin cannot
9
+ * register into. The shadow therefore works the same way the hero
10
+ * headline patch does (Brand.tsx): a CSS patch injected into the head,
11
+ * anchored on the row's stable literal attribute [data-chat-running]
12
+ * (RunningStatus.tsx writes it directly; the class names around it are
13
+ * CSS-module hashes, so they are matched by the same [class*="…"]
14
+ * substring selectors the hero patch already relies on).
15
+ *
16
+ * The patch hides both whale layers and repaints the runningIcon box with
17
+ * the AI Shell mark (a data URI background), adding a gentle bob/pulse
18
+ * that follows the official whale's degradation: under
19
+ * prefers-reduced-motion the mark is static, matching the official
20
+ * whale's own reduced-motion fallback.
21
+ *
22
+ * Shadowing is a default, not a force (plan §2.2): install() and the
23
+ * disposer it returns are symmetric, so disabling the plugin restores
24
+ * the official whale exactly.
25
+ *
26
+ * @module @huaweidevtools/aishell-dsh-brand/client/running-mark-shadow
27
+ */
28
+ /** The disposer removes the patch style element; a no-op install returns this. */
29
+ export type RunningMarkShadowDisposer = () => void;
30
+ /** Marker attribute the patch style element carries (exposed for the spec). */
31
+ export declare const RUNNING_PATCH_MARKER = "data-aishell-running-mark";
32
+ /**
33
+ * The patch CSS. The whale layers are hidden unconditionally — the static
34
+ * fallback too, because the mark replaces both — while the mark's own
35
+ * animation is guarded so reduced motion keeps it still.
36
+ */
37
+ export declare const RUNNING_MARK_PATCH_CSS: string;
38
+ /**
39
+ * Install the running-mark shadow on a document.
40
+ *
41
+ * Injects the patch CSS once (guarded by the marker attribute so a second
42
+ * install is a no-op) and returns the disposer that removes the element.
43
+ */
44
+ export declare function installRunningMarkShadow(target: Document): RunningMarkShadowDisposer;
45
+ //# sourceMappingURL=running-mark-shadow.d.ts.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Running-status copy shadow for the branded distribution.
3
+ *
4
+ * ui-chat's locale dictionary names the live running line after DeepSeek's
5
+ * own product language: zh "深度求索中" / "深度求索中,用时 {duration} ···",
6
+ * en "Deep diving" / "Deep diving for {duration} ···". locale.register
7
+ * throws on an already-claimed namespace, so the `chat` dictionary cannot
8
+ * be re-registered — the shadow rewrites the rendered text instead.
9
+ *
10
+ * The row re-renders every second while a Turn runs (LIVE_RUN_CLOCK_
11
+ * INTERVAL_MS = 1000), so a one-shot rewrite would flash the official
12
+ * copy on every tick. A MutationObserver watches [data-chat-running]
13
+ * subtrees — characterData for the second ticks, childList for mounts,
14
+ * and the data-shimmer-text attribute because TextShimmer paints its
15
+ * highlight layer through CSS `content: attr(data-shimmer-text)`: a text
16
+ * node rewritten without the attribute would keep showing the old glyphs
17
+ * every time the highlight sweeps across them.
18
+ *
19
+ * One substring replacement covers both keys: the duration form begins
20
+ * with the same prefix as the bare form, so replacing "深度求索中" →
21
+ * "思考中" and "Deep diving" → "Thinking" rewrites the long sentences
22
+ * too. The row's aria-hidden status span carries the same text and sits
23
+ * inside the same subtree, so it is covered by the same sweep. Duration
24
+ * units and the "···" tail are the harness's own words, not product
25
+ * branding, and pass through untouched.
26
+ *
27
+ * Shadowing is a default, not a force (plan §2.2): install() and the
28
+ * disposer it returns are symmetric, so disabling the plugin restores
29
+ * the official copy exactly.
30
+ *
31
+ * @module @huaweidevtools/aishell-dsh-brand/client/running-text-shadow
32
+ */
33
+ /** The disposer stops the observer and restores the official copy; a no-op install returns this. */
34
+ export type RunningTextShadowDisposer = () => void;
35
+ /**
36
+ * Rewrite the running-line copy inside a value.
37
+ *
38
+ * Every occurrence of either official phrase is replaced; a value without
39
+ * them is returned as-is — the shadow never invents content.
40
+ */
41
+ export declare function rebrandRunningText(value: string): string;
42
+ /**
43
+ * Install the running-text shadow on a document.
44
+ *
45
+ * Sweeps every mounted [data-chat-running] row once, then observes the
46
+ * document body for new rows (childList) and their text/attribute
47
+ * changes (characterData, data-shimmer-text) so the per-second re-renders
48
+ * stay covered for as long as the row exists. Returns the disposer: the
49
+ * observer stops and every live row is restored to its official copy.
50
+ */
51
+ export declare function installRunningTextShadow(target: Document): RunningTextShadowDisposer;
52
+ //# sourceMappingURL=running-text-shadow.d.ts.map