refactor: apply repository naming contract
Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
/* Directory-browser dialog (figma 813-23126 family). The shared Modal renders
|
||||
* headless here — mask, card, Escape only — and this module owns the figma
|
||||
* frame: 680×500 card (viewport-clamped; upsized from the figma 600×420),
|
||||
* header (title + crumbs, l3 separator),
|
||||
* the one-or-two-column Miller content, and the bordered footer. */
|
||||
|
||||
/* Doubled class beats Modal's own .dialog regardless of stylesheet order. */
|
||||
/* Short viewports clamp the card: header/footer are flex-none and the
|
||||
* columns scroll, so shrinking the height keeps Open/Cancel reachable
|
||||
* instead of clipping them below a fixed overlay. */
|
||||
.dialog.dialog {
|
||||
width: min(680px, 100%);
|
||||
height: min(500px, calc(100dvh - 32px));
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
/* The Modal card is an l2 surface and the columns below scroll on it:
|
||||
* rebind the scrollbar indirection to the elevation pair here, on the
|
||||
* surface, so it inherits down to whichever descendant scrolls (the
|
||||
* rebinding contract in ui-theme styles/scrollbar.css). */
|
||||
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2);
|
||||
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2);
|
||||
}
|
||||
|
||||
/* Card-scope wrapper hosting the path editor's Escape and focus-leave
|
||||
* observers; display:contents keeps header/content/footer as direct flex
|
||||
* children of the Modal card. */
|
||||
.editorScope {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
/* Header block: pl24 pr14 pt16 pb8, 8px between title row and crumb row. */
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
flex: none;
|
||||
padding: 16px 14px 8px 24px;
|
||||
border-bottom: 1px solid var(--dsw-alias-border-l3);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
min-height: 28px;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 510;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
/* The bar IS the editor's box in both modes: it carries the rounded outline
|
||||
* and the inner padding, the crumbs and the input sit inside it, and hovering
|
||||
* the edit zone lights the whole row rather than the remainder right of the
|
||||
* crumbs. The negative left margin pays back the border and padding, so the
|
||||
* crumb (and input) text keeps the column the title sits in. */
|
||||
.crumbBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
min-height: 24px;
|
||||
margin-left: -9px;
|
||||
padding: 0 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/* Lit by the affordance the row belongs to, never by a crumb: a crumb's hover
|
||||
* offers navigation, not path entry. Editing keeps the outline standing. */
|
||||
.crumbBar:has(.crumbEditZone:enabled:hover),
|
||||
.crumbBar:has(.crumbEditZone:focus-visible),
|
||||
.crumbBar:has(.pathInput) {
|
||||
border-color: var(--dsw-alias-border-l2);
|
||||
}
|
||||
|
||||
/* Deep chains scroll inside the trail (the effect pins the tail into view)
|
||||
* so the edit zone to the right never leaves the bar. */
|
||||
/* The Miller columns keep their own row so a status/error line below never
|
||||
* competes with the fixed column widths for horizontal space. */
|
||||
/* A narrow viewport shrinks the dialog below two fixed panes; the row
|
||||
* scrolls horizontally (the effect pins the child pane into view) so
|
||||
* descent never hides behind the Modal's clipping. */
|
||||
.millerRow {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
/* 12px of row gap on each side of the divider; the left side reads wider
|
||||
* by the column's trailing 8px scrollbar clearance, which is deliberate —
|
||||
* the thumb needs that room, the right pane's rows do not. */
|
||||
gap: 12px;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.crumbTrail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex: 0 1 auto;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.crumbSeat {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex: none;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.crumb {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
max-width: 160px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.crumb:hover {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.crumbChevron {
|
||||
flex: none;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
/* The empty remainder of the bar: a real click target that flips the bar into
|
||||
* path-edit mode. The pencil glyph seated at its right edge is the standing
|
||||
* affordance; the outline the gesture lights belongs to the bar, so the whole
|
||||
* row reads as the box the input will occupy. */
|
||||
.crumbEditZone {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 1 0 34px;
|
||||
min-width: 34px;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: text;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.crumbEditGlyph {
|
||||
flex: none;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
.crumbEditZone:enabled:hover .crumbEditGlyph,
|
||||
.crumbEditZone:focus-visible .crumbEditGlyph {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.crumbEditZone:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.crumbEditZone:disabled .crumbEditGlyph {
|
||||
color: var(--dsw-alias-label-caption);
|
||||
}
|
||||
|
||||
/* Chrome-free: the bar around it draws the box (border, radius, padding). */
|
||||
.pathInput {
|
||||
box-sizing: border-box;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
height: 22px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
/* Miller content: symmetric 16px vertical padding so the divider clears the
|
||||
* header and footer rules evenly; each column scrolls alone (column widths
|
||||
* live at .column). */
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
/* Anchors the floating loading pill (.loadingFloat). */
|
||||
position: relative;
|
||||
/* Right inset is slimmer than the left: the trailing column's own 8px
|
||||
* scrollbar clearance makes up the optical difference. */
|
||||
padding: 16px 16px 16px 24px;
|
||||
}
|
||||
|
||||
/* Columns split the row evenly around the divider (a solo column takes the
|
||||
* whole row); 256px is the floor below which the row scrolls (scrollbar
|
||||
* hidden, the effect pins the child pane into view) instead of squeezing
|
||||
* the panes. */
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
flex: 1 1 0;
|
||||
min-width: 256px;
|
||||
overflow-y: auto;
|
||||
/* The themed scrollbar occupies the column's edge (styled scrollbars are
|
||||
* classic, gutter-taking ones); the extra clearance keeps the row pills
|
||||
* clear of the thumb. */
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.divider {
|
||||
flex: none;
|
||||
width: 1px;
|
||||
background: var(--dsw-alias-border-l3);
|
||||
}
|
||||
|
||||
.rowSeat {
|
||||
display: flex;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: 28px;
|
||||
flex: none;
|
||||
padding: 4px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.row:hover {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
/* Selection: pill fill + the open-folder glyph in the info accent. */
|
||||
.rowSelected,
|
||||
.rowSelected:hover {
|
||||
background: var(--dsw-alias-interactive-bg-active, var(--dsw-alias-interactive-bg-hover));
|
||||
}
|
||||
|
||||
.rowIcon {
|
||||
flex: none;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
}
|
||||
|
||||
.rowIconSelected {
|
||||
flex: none;
|
||||
color: var(--dsw-alias-button-info-fill);
|
||||
}
|
||||
|
||||
.rowName {
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.rowChevron {
|
||||
flex: none;
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
.status,
|
||||
.error {
|
||||
padding: 4px;
|
||||
/* The loading pill occupies the opposite corner while a stale status stays
|
||||
* visible. Reserve its widest localized footprint so wrapped text cannot
|
||||
* run underneath it on a narrow card. */
|
||||
padding-right: 120px;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--dsw-alias-state-error-primary);
|
||||
}
|
||||
|
||||
/* The slow-scan indicator floats over the content's bottom-RIGHT corner on
|
||||
* the card background instead of occupying a row: a scan must never shift
|
||||
* the columns' height, and the stale view keeps rendering beneath it (it
|
||||
* only appears at all once a scan outlives SLOW_SCAN_DELAY_MS). Right,
|
||||
* not left: the truncated/error status rows flow at the bottom LEFT and
|
||||
* stay on screen through a scan, with their reserved right padding keeping
|
||||
* both legible even on a narrow card. After .status in the cascade — the
|
||||
* element carries both classes and this padding must win the
|
||||
* same-specificity race. */
|
||||
.loadingFloat {
|
||||
position: absolute;
|
||||
right: 16px;
|
||||
bottom: 8px;
|
||||
padding: 2px 8px;
|
||||
background: var(--dsw-alias-bg-layer-2);
|
||||
}
|
||||
|
||||
/* Footer: l3 separator on top, symmetric padding so the row sits vertically
|
||||
* centered in the bar; New-folder and the show-hidden toggle pin left. */
|
||||
.footerBar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/* Narrow viewports wrap the confirm/cancel pair onto their own row
|
||||
* instead of clipping Open past the card's hidden overflow. */
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
flex: none;
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid var(--dsw-alias-border-l3);
|
||||
}
|
||||
|
||||
/* Show-hidden toggle: a subtle fixed-label text button left of the gap;
|
||||
* the pressed state seats a check glyph after the label (Menu's selected
|
||||
* vocabulary; trailing so the label never shifts) instead of flipping the
|
||||
* wording. */
|
||||
.showHiddenToggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--dsw-alias-label-secondary);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.showHiddenToggle:hover {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.showHiddenToggle:disabled {
|
||||
color: var(--dsw-alias-label-caption);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.showHiddenToggleActive {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.footerGap {
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
.footerAction {
|
||||
min-width: 72px;
|
||||
}
|
||||
|
||||
/* Nested create dialog (figma 813:23278): a small centered card. */
|
||||
.createDialog.createDialog {
|
||||
width: min(380px, 100%);
|
||||
padding: 0;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.createBody {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
padding: 22px 24px 20px;
|
||||
}
|
||||
|
||||
.createTitle {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
font-weight: 510;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.createIn {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.createInput {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
padding: 7px 14px;
|
||||
border: 1px solid var(--dsw-alias-border-l2);
|
||||
border-radius: 22px;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
}
|
||||
|
||||
.createInput::placeholder {
|
||||
color: var(--dsw-alias-label-caption);
|
||||
}
|
||||
|
||||
.createActions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* The browse picking occupant (package-internal; the `./client` surface
|
||||
* exposes only the Loader exports). Same-package tests exercise it directly
|
||||
* through this module.
|
||||
*/
|
||||
import { createElement } from 'react'
|
||||
import type { ReactElement } from 'react'
|
||||
import type { DirectoryListing } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { Translate } from '@deepseek-ai/dsh-client-locale/client'
|
||||
// Type-only: the owner contract of the directory-flow holes.
|
||||
import type { DirectoryFlowOwnerProps } from '@deepseek-ai/dsh-client-ui-workspace/client'
|
||||
import { DirectoryBrowser } from './DirectoryBrowser.tsx'
|
||||
|
||||
/** Injected face: the browse wire calls and copy the dialog drives (bound in apply's closure). */
|
||||
export interface BrowseFlowInjected {
|
||||
/** List one directory level (absent path = the Host home directory); the signal aborts a superseded scan. */
|
||||
listDirectory: (path?: string, signal?: AbortSignal) => Promise<DirectoryListing>
|
||||
/** Create one child directory under an existing parent. */
|
||||
createDirectory: (path: string, name: string) => Promise<string>
|
||||
/** Localized dialog copy (this package's namespace). */
|
||||
t: Translate
|
||||
}
|
||||
|
||||
/**
|
||||
* Flow occupant: adapts the hole's owner conversation onto the browser
|
||||
* dialog — a confirmed directory is the picked path, dismissal is the
|
||||
* cancellation. Browse failures (unreadable targets, create conflicts) stay
|
||||
* inside the dialog's own alert surfaces, so the owner's `onError` arm is
|
||||
* never driven by this occupant.
|
||||
* @param props - owner conversation plus the injected browse face.
|
||||
* @returns the dialog element (renders nothing while closed).
|
||||
*/
|
||||
export function BrowseDirectoryFlow(props: DirectoryFlowOwnerProps & BrowseFlowInjected): ReactElement {
|
||||
return createElement(DirectoryBrowser, {
|
||||
open: props.open,
|
||||
busy: props.busy,
|
||||
listDirectory: props.listDirectory,
|
||||
createDirectory: props.createDirectory,
|
||||
t: props.t,
|
||||
onOpen: props.onPicked,
|
||||
onClose: props.onCancel,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Browser half of the browse directory-picker backend: fills ui-workspace's
|
||||
* two directory-flow holes with the in-app Select Workspace Directory dialog
|
||||
* (figma `Harness` 813-23126 family), driving the node half's
|
||||
* `host.listDirectory`/`host.createDirectory` primitives. Mounting this
|
||||
* package therefore composes both sides of the browse interaction with one
|
||||
* cordis.yml row; no client code branches on a capability kind. The dialog's
|
||||
* copy is locale-registered here — the flow package owns its own strings.
|
||||
*/
|
||||
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
// Type-only: pulls the SlotMap merge declaring the directory-flow holes.
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-workspace/client'
|
||||
import type { BrowseFlowInjected } from './flow.ts'
|
||||
import { BrowseDirectoryFlow } from './flow.ts'
|
||||
|
||||
/** Locale namespace owning the browser dialog's copy. */
|
||||
const LOCALE_NS = 'directory-browser'
|
||||
|
||||
/** Required services (cordis fiber inject): the slot registry, the wire-facing workspace service, and locale. */
|
||||
export const inject = ['slots', 'workspaces', 'locale']
|
||||
|
||||
/**
|
||||
* Client plugin body: register the dialog's dictionaries and the browse flow
|
||||
* into both directory-flow holes through `slots.inject()` because the
|
||||
* ui-workspace entries may activate later or replace their declarations.
|
||||
* @param ctx - client root context.
|
||||
*/
|
||||
export function apply(ctx: ClientContext): void {
|
||||
ctx.effect(() => {
|
||||
// The two dictionaries land as a unit: if the second registration hits a
|
||||
// rival owner of the namespace, the first rolls back before the throw —
|
||||
// a failed activation must not squat the namespace's other locale.
|
||||
const disposers: (() => void)[] = []
|
||||
const dictionaries: [locale: string, dict: Record<string, string>][] = [
|
||||
['zh', {
|
||||
'browser.title': '选择工作区目录',
|
||||
'browser.home': '主目录',
|
||||
'browser.newFolder': '新建文件夹',
|
||||
'browser.folderName': '文件夹名称',
|
||||
'browser.createIn': '在"{name}"中新建文件夹',
|
||||
'browser.untitledFolder': '未命名文件夹',
|
||||
'browser.create': '创建',
|
||||
'browser.cancel': '取消',
|
||||
'browser.open': '打开',
|
||||
'browser.editPath': '编辑路径',
|
||||
'browser.loading': '加载中…',
|
||||
'browser.truncated': '文件夹过多,仅显示开头部分。',
|
||||
'browser.showHidden': '显示隐藏文件',
|
||||
}],
|
||||
['en', {
|
||||
'browser.title': 'Select Workspace Directory',
|
||||
'browser.home': 'Home',
|
||||
'browser.newFolder': 'New folder',
|
||||
'browser.folderName': 'Folder name',
|
||||
'browser.createIn': 'New folder in "{name}"',
|
||||
'browser.untitledFolder': 'Untitled folder',
|
||||
'browser.create': 'Create',
|
||||
'browser.cancel': 'Cancel',
|
||||
'browser.open': 'Open',
|
||||
'browser.editPath': 'Edit path',
|
||||
'browser.loading': 'Loading…',
|
||||
'browser.truncated': 'Too many folders to list; only the beginning is shown.',
|
||||
'browser.showHidden': 'Show hidden files',
|
||||
}],
|
||||
]
|
||||
try {
|
||||
for (const [locale, dict] of dictionaries) disposers.push(ctx.locale.register(LOCALE_NS, locale, dict))
|
||||
} catch (error) {
|
||||
for (const dispose of disposers.reverse()) dispose()
|
||||
throw error
|
||||
}
|
||||
return () => { for (const dispose of disposers) dispose() }
|
||||
}, 'directory-picker-browse: dialog dictionaries')
|
||||
|
||||
const injected = (): BrowseFlowInjected => ({
|
||||
listDirectory: (path, signal) => ctx.workspaces.listDirectory(path, signal),
|
||||
createDirectory: (path, name) => ctx.workspaces.createDirectory(path, name),
|
||||
t: ctx.locale.bind(LOCALE_NS),
|
||||
})
|
||||
// Both declaration lifetimes must be live before the pair installs; the
|
||||
// generator makes the two registrations one transactional effect. The
|
||||
// outer/inner nesting order is arbitrary; neither hole has precedence.
|
||||
ctx.slots.inject('conversation.hero.workspace.directoryFlow', () =>
|
||||
ctx.slots.inject('sidebar.workspaces.directoryFlow', function* () {
|
||||
yield ctx.slots.register({
|
||||
name: 'conversation.hero.workspace.directoryFlow', inject: injected,
|
||||
}, BrowseDirectoryFlow)
|
||||
yield ctx.slots.register({
|
||||
name: 'sidebar.workspaces.directoryFlow', inject: injected,
|
||||
}, BrowseDirectoryFlow)
|
||||
}))
|
||||
}
|
||||
6
packages/client/ui-directory-picker-browse/src/css-modules.d.ts
vendored
Normal file
6
packages/client/ui-directory-picker-browse/src/css-modules.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare module '*.module.css' {
|
||||
const classes: Record<string, string>
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module '*.css'
|
||||
10
packages/client/ui-directory-picker-browse/src/index.ts
Normal file
10
packages/client/ui-directory-picker-browse/src/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Directory-picker browsing surface, node half. Pure UI plugin: the empty
|
||||
* apply exists so the plugin appears in the host cordis.yml / Loader; the
|
||||
* browser half ships via exports["./client"], discovered through the
|
||||
* package.json dsh.client declaration. The listing and creation primitives it
|
||||
* drives live in `@deepseek-ai/dsh-host-directory-picker-browse`.
|
||||
*/
|
||||
|
||||
/** Host plugin body — no host-side behavior for this surface plugin. */
|
||||
export function apply(): void {}
|
||||
31
packages/client/ui-directory-picker-browse/src/invariant.ts
Normal file
31
packages/client/ui-directory-picker-browse/src/invariant.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-directory-picker-browse`.
|
||||
* @module @deepseek-ai/dsh-client-ui-directory-picker-browse/invariant
|
||||
*/
|
||||
|
||||
/* jscpd:ignore-start */
|
||||
import type { Context } from '@deepseek-ai/cordis'
|
||||
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
|
||||
|
||||
const PACKAGE_NAME = '@deepseek-ai/dsh-client-ui-directory-picker-browse'
|
||||
|
||||
/** Cordis companion plugin name. */
|
||||
export const name = 'client-ui-directory-picker-browse-invariant'
|
||||
/** Service required before the companion can reserve package ownership. */
|
||||
export const inject = ['invariants']
|
||||
|
||||
/**
|
||||
* No runtime invariant: the plugin registers one workspace directory-flow
|
||||
* owner whose disposal the HMR-safety spec proves, and every listing it shows
|
||||
* is re-read from the Host on demand rather than held here.
|
||||
*/
|
||||
const install: InvariantInstaller = () => {}
|
||||
|
||||
/**
|
||||
* Register this package's invariant companion.
|
||||
* @param ctx - Cordis context carrying the invariant service.
|
||||
* @returns the installed registration's disposer after setup succeeds.
|
||||
*/
|
||||
export const apply = (ctx: Context): Promise<() => void> =>
|
||||
Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
|
||||
/* jscpd:ignore-end */
|
||||
Reference in New Issue
Block a user