feat(web): open the local settings file
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/ui-settings/README.md
|
||||
README.md: 14c78c83467313a6efa7033c31fb9c9b1cd94e0c
|
||||
README.zh.md: 8831842d03db4572f1dca5547b84c0d8a3be8f2d
|
||||
README.md: de78d599b7833179339ceeb680fbd665b056bd83
|
||||
README.zh.md: fdc6ade1413c7d02afba85ed3b69a2b78b401836
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Settings shell plugin: a pure composition face. It occupies `sidebar.settings` with the trigger chrome and modal settings panel, and declares the slots registrants fill: `settings.trigger` / `settings.header` / `settings.close` (chrome content), `settings.section` (one page per feature), and `settings.onboarding` (ordered feature-owned pages in a full-viewport stage). The shell ships no copy of its own — all text arrives from registrants (ui-settings-general owns chrome, General, and the product notice; features own their sections, rows, and conditional onboarding pages). Nav labels may be locale-following thunks, so the nav projection resolves them through `resolveSlotLabel` and re-renders on the section ledger bump or the locale revision (an optional `ctx.get('locale')` read; no hard locale dependency).
|
||||
Settings shell plugin: a pure composition face. It occupies `sidebar.settings` with the trigger chrome and modal settings panel, and declares the slots registrants fill: `settings.trigger` / `settings.header` / `settings.close` (chrome content), `settings.action` (ordered content-header actions), `settings.section` (one page per feature), and `settings.onboarding` (ordered feature-owned pages in a full-viewport stage). The shell ships no copy of its own — all text arrives from registrants (ui-settings-general owns chrome, General, and the product notice; features own their actions, sections, rows, and conditional onboarding pages). Nav labels may be locale-following thunks, so the nav projection resolves them through `resolveSlotLabel` and re-renders on the section ledger bump or the locale revision (an optional `ctx.get('locale')` read; no hard locale dependency).
|
||||
|
||||
The shell projects the onboarding ledger into ascending order and mounts exactly one page at a time in a body-level stage while marking the underlying app root inert. The active registrant receives its id, `complete()`, and an `openSection(id)` callback; completing or skipping transfers ownership to the next entry. Registrants own durable completion, capability readiness, copy, and mutations, so independently registered flows cannot stack and the shell does not become a second configuration fact source.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
设置外壳插件:一个纯组合表层。它以触发控件和模态设置面板占用 `sidebar.settings`,并声明由注册方填充的 slot:`settings.trigger`/`settings.header`/`settings.close`(界面框架内容)、`settings.section`(每项功能一页)和 `settings.onboarding`(由各功能持有、显示在全视口展示层中的有序页面)。外壳不自带文案:所有文本都来自注册方(ui-settings-general 拥有界面框架、「通用」分区和产品声明;各功能拥有各自的分区、行和条件式首次使用引导页面)。导航 label 可以是跟随语言的 thunk,因此导航投影经 `resolveSlotLabel` 解析,并在分区账本更新或 locale revision 变化时重新渲染(`ctx.get('locale')` 可选读取,无硬 locale 依赖)。
|
||||
设置外壳插件:一个纯组合表层。它以触发控件和模态设置面板占用 `sidebar.settings`,并声明由注册方填充的 slot:`settings.trigger`/`settings.header`/`settings.close`(界面框架内容)、`settings.action`(内容标题栏中的有序操作)、`settings.section`(每项功能一页)和 `settings.onboarding`(由各功能持有、显示在全视口展示层中的有序页面)。外壳不自带文案:所有文本都来自注册方(ui-settings-general 拥有界面框架、「通用」分区和产品声明;各功能拥有各自的操作、分区、行和条件式首次使用引导页面)。导航 label 可以是跟随语言的 thunk,因此导航投影经 `resolveSlotLabel` 解析,并在分区账本更新或 locale revision 变化时重新渲染(`ctx.get('locale')` 可选读取,无硬 locale 依赖)。
|
||||
|
||||
外壳将首次使用引导记录按升序投影,在 body 层级的展示层中每次只挂载一个页面,同时将下层应用根节点标记为 `inert`。当前注册方会收到该条目的 id、`complete()` 和 `openSection(id)` 回调;完成或跳过当前页面后,所有权转交给下一项。持久化完成状态、能力就绪状态、文案和变更操作均由注册方持有,因此独立注册的流程无法堆叠,外壳也不会成为第二个配置事实来源。
|
||||
|
||||
|
||||
@@ -167,12 +167,22 @@
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
height: 54px;
|
||||
padding: 20px 14px 8px 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.actions {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Close button (figma .Icon_container 501:29982): 28x28, r28, 14px glyph. */
|
||||
.close {
|
||||
display: inline-flex;
|
||||
|
||||
@@ -76,6 +76,7 @@ function SettingsPanel({ rows, renderSlot, activeId, onSelect, onClose }: PanelP
|
||||
</nav>
|
||||
<div className={css.content}>
|
||||
<div className={css.header}>
|
||||
<div className={css.actions}>{renderSlot('settings.action', {})}</div>
|
||||
<button ref={closeButton} type="button" className={css.close} onClick={onClose}>
|
||||
<IconCloseOutline16 size={14} />
|
||||
<span className={css.hiddenLabel}>{renderSlot('settings.close', {})}</span>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
* Settings shell slot contract — the canonical home of every settings slot
|
||||
* type. The shell is a pure composition face with zero copy of its own: it
|
||||
* occupies the sidebar-owned `sidebar.settings` hole and declares the slots
|
||||
* below; ALL text (trigger label, panel title, close aria, section content)
|
||||
* arrives from registrants. A feature owns its settings surface — adding a
|
||||
* below; ALL text (trigger label, panel title, header actions, close aria,
|
||||
* section content) arrives from registrants. A feature owns its settings surface — adding a
|
||||
* setting never means editing the shell; copy that belongs to no single
|
||||
* feature (chrome, the General section) is owned by ui-settings-general.
|
||||
*/
|
||||
@@ -29,6 +29,12 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
* Absent contribution leaves the heading empty.
|
||||
*/
|
||||
'settings.header': { kind: 'single'; scope: 'root'; owner: SettingsHeaderOwnerProps }
|
||||
/**
|
||||
* Optional actions rendered in the content-column header before Close.
|
||||
* Registrants own visibility, behavior, copy, and failure presentation;
|
||||
* the shell supplies only the ordered render site.
|
||||
*/
|
||||
'settings.action': { kind: 'list'; scope: 'root'; owner: SettingsHeaderOwnerProps }
|
||||
/**
|
||||
* The close button's visually-hidden label text (the button itself —
|
||||
* icon, geometry, focus — is shell chrome). Absent contribution leaves
|
||||
@@ -125,6 +131,11 @@ export type SettingsRootInjected = {
|
||||
export type SettingsRootComponentProps =
|
||||
PropsRuntime<'sidebar.settings'>
|
||||
& PropsRenderSlots<
|
||||
'settings.trigger' | 'settings.header' | 'settings.close' | 'settings.section' | 'settings.onboarding'
|
||||
| 'settings.trigger'
|
||||
| 'settings.header'
|
||||
| 'settings.action'
|
||||
| 'settings.close'
|
||||
| 'settings.section'
|
||||
| 'settings.onboarding'
|
||||
>
|
||||
& InjectFace<SettingsRootInjected>
|
||||
|
||||
@@ -103,6 +103,7 @@ export function apply(ctx: ClientContext): void {
|
||||
children: {
|
||||
'settings.trigger': { kind: 'single', scope: 'root' },
|
||||
'settings.header': { kind: 'single', scope: 'root' },
|
||||
'settings.action': { kind: 'list', scope: 'root' },
|
||||
'settings.close': { kind: 'single', scope: 'root' },
|
||||
'settings.section': { kind: 'list', scope: 'root' },
|
||||
'settings.onboarding': { kind: 'list', scope: 'root' },
|
||||
|
||||
@@ -24,10 +24,11 @@ function injectedOf(slots: SlotsService): SettingsRootInjected {
|
||||
return (entry.inject as () => SettingsRootInjected)()
|
||||
}
|
||||
|
||||
/** The shell's five child declarations (chrome, sections, and onboarding overlays). */
|
||||
/** The shell's child declarations (chrome, actions, sections, and onboarding overlays). */
|
||||
const CHILD_SPECS = {
|
||||
'settings.trigger': { kind: 'single', scope: 'root' },
|
||||
'settings.header': { kind: 'single', scope: 'root' },
|
||||
'settings.action': { kind: 'list', scope: 'root' },
|
||||
'settings.close': { kind: 'single', scope: 'root' },
|
||||
'settings.section': { kind: 'list', scope: 'root' },
|
||||
'settings.onboarding': { kind: 'list', scope: 'root' },
|
||||
@@ -38,7 +39,7 @@ describe('ui-settings apply', () => {
|
||||
expect(inject).toEqual(['slots'])
|
||||
})
|
||||
|
||||
it('registers the shell and declares the five child slots, before or after the declaration', async () => {
|
||||
it('registers the shell and declares every child slot, before or after the declaration', async () => {
|
||||
const before = await bench()
|
||||
declare(before.slots)
|
||||
await before.ctx.plugin({ inject: [...inject], apply }).await()
|
||||
@@ -124,7 +125,7 @@ describe('ui-settings apply', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('unregisters the shell and collapses all five child slots on teardown', async () => {
|
||||
it('unregisters the shell and collapses every child slot on teardown', async () => {
|
||||
const b = await bench()
|
||||
declare(b.slots)
|
||||
const fiber = b.ctx.plugin({ inject: [...inject], apply })
|
||||
|
||||
@@ -14,6 +14,7 @@ type Step = { id: string; order: number }
|
||||
const SEAT_CONTENT: Record<string, string> = {
|
||||
'settings.trigger': 'Settings',
|
||||
'settings.header': 'Settings Title',
|
||||
'settings.action': 'Open configuration file',
|
||||
'settings.close': 'Close',
|
||||
}
|
||||
|
||||
@@ -114,6 +115,13 @@ describe('SettingsPanel chrome seats', () => {
|
||||
expect(close.hasAttribute('aria-label')).toBe(false)
|
||||
expect(close.textContent).toContain('Close')
|
||||
})
|
||||
|
||||
it('renders header actions before the shell-owned close control', () => {
|
||||
const { renderSlot } = mount()
|
||||
openPanel()
|
||||
expect(screen.getByText('Open configuration file')).toBeTruthy()
|
||||
expect(renderSlot).toHaveBeenCalledWith('settings.action', {})
|
||||
})
|
||||
})
|
||||
|
||||
describe('SettingsPanel close paths', () => {
|
||||
|
||||
Reference in New Issue
Block a user