@zq-silk/yui 2.15.0 → 2.17.0

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 (104) hide show
  1. package/dist/cli/commandCatalog.js +17 -11
  2. package/dist/cli/interactionCandidates.js +20 -5
  3. package/dist/cli/interactionPolicy.js +5 -4
  4. package/dist/cli/roleWizard.js +7 -0
  5. package/dist/commands/globalRoleCommands.js +13 -2
  6. package/dist/commands/operatorCommands.js +2 -0
  7. package/dist/commands/profileCommands.js +19 -2
  8. package/dist/commands/roleConfiguration.js +3 -0
  9. package/dist/commands/taskCommands.js +16 -0
  10. package/dist/commands/taskRoleRuntimeStatus.js +3 -0
  11. package/dist/controller/agentJobControl.js +9 -1
  12. package/dist/controller/fileSchedulerStoreAdapter.js +35 -4
  13. package/dist/controller/globalInputDelivery.js +14 -8
  14. package/dist/controller/providerRetryAdmission.js +9 -0
  15. package/dist/controller/providerRetryDelivery.js +16 -0
  16. package/dist/controller/runtime.js +14 -0
  17. package/dist/controller/runtimeLaunchCoordinator.js +3 -1
  18. package/dist/executor/fileRoleLaunchPlanner.js +3 -0
  19. package/dist/message/messageContinuation.js +4 -0
  20. package/dist/output/rolePresentation.js +3 -0
  21. package/dist/profile/agentProfile.js +8 -0
  22. package/dist/profile/agentProfileRuntime.js +16 -1
  23. package/dist/review/reviewDecision.js +6 -0
  24. package/dist/role/availability.js +28 -0
  25. package/dist/role/role.js +6 -0
  26. package/dist/runtime/codexNativeAccess.js +21 -4
  27. package/dist/runtime/tmuxAdapters.js +5 -0
  28. package/dist/scheduler/activeRoleRunDelivery.js +9 -0
  29. package/dist/scheduler/leaderWakeupProcessor.js +8 -1
  30. package/dist/scheduler/roleRunLiveness.js +3 -0
  31. package/dist/storage/sqliteSchema.js +10 -0
  32. package/dist/storage/storageVersions.js +1 -1
  33. package/dist/web/assets/client/app/catalog.js +40 -14
  34. package/dist/web/assets/client/app/main.js +43 -26
  35. package/dist/web/assets/client/app/manage.js +18 -0
  36. package/dist/web/assets/client/app/selection.js +4 -1
  37. package/dist/web/assets/client/app/taskView.js +29 -16
  38. package/dist/web/assets/client/i18n/en.js +72 -21
  39. package/dist/web/assets/client/i18n/zh-CN.js +72 -21
  40. package/dist/web/assets/client/index.js +6 -0
  41. package/dist/web/assets/client/layout/manageShell.js +30 -8
  42. package/dist/web/assets/client/layout/shortcuts.js +9 -5
  43. package/dist/web/assets/client/layout/sidebarNav.js +147 -0
  44. package/dist/web/assets/client/layout/sizing.js +5 -2
  45. package/dist/web/assets/client/layout/workspace.js +193 -107
  46. package/dist/web/assets/client/lib/motion.js +59 -0
  47. package/dist/web/assets/client/lib/prefs.js +29 -2
  48. package/dist/web/assets/client/lib/theme.js +11 -4
  49. package/dist/web/assets/client/ui/primitives.js +40 -0
  50. package/dist/web/assets/client/views/dock/conversation.js +421 -123
  51. package/dist/web/assets/client/views/dock/session.js +229 -0
  52. package/dist/web/assets/client/views/dock/terminal.js +48 -39
  53. package/dist/web/assets/client/views/overview.js +8 -5
  54. package/dist/web/assets/client/views/task/page.js +7 -17
  55. package/dist/web/assets/shared/brand.js +22 -0
  56. package/dist/web/assets/shared/geometry.js +7 -1
  57. package/dist/web/assets/shared/icons.js +4 -0
  58. package/dist/web/assets/shared/motion.js +74 -0
  59. package/dist/web/assets/shell/appRail.js +14 -0
  60. package/dist/web/assets/shell/dialogs.js +1 -0
  61. package/dist/web/assets/shell/dock.js +19 -24
  62. package/dist/web/assets/shell/index.js +6 -2
  63. package/dist/web/assets/shell/login.js +9 -8
  64. package/dist/web/assets/shell/manage.js +32 -20
  65. package/dist/web/assets/shell/settingsContent.js +1 -1
  66. package/dist/web/assets/shell/sidebar.js +16 -23
  67. package/dist/web/assets/styles/base.js +1 -1
  68. package/dist/web/assets/styles/index.js +2 -0
  69. package/dist/web/assets/styles/layout/responsive.js +63 -19
  70. package/dist/web/assets/styles/layout/workspace.js +53 -16
  71. package/dist/web/assets/styles/motion.js +51 -0
  72. package/dist/web/assets/styles/tokens.js +5 -3
  73. package/dist/web/assets/styles/ui/controls.js +1 -1
  74. package/dist/web/assets/styles/ui/forms.js +1 -1
  75. package/dist/web/assets/styles/ui/surfaces.js +1 -2
  76. package/dist/web/assets/styles/views/conversation.js +97 -18
  77. package/dist/web/assets/styles/views/dock.js +34 -2
  78. package/dist/web/assets/styles/views/login.js +16 -13
  79. package/dist/web/assets/styles/views/manage.js +25 -42
  80. package/dist/web/assets/styles/views/page.js +2 -3
  81. package/dist/web/assets/styles/views/records.js +1 -1
  82. package/dist/web/assets/styles/views/settings.js +1 -1
  83. package/dist/web/assets/styles/views/sidebar.js +25 -20
  84. package/dist/web/assets/styles/views/task.js +8 -15
  85. package/dist/web/assets/styles/views/terminal.js +13 -9
  86. package/dist/web/webConversation.js +1 -0
  87. package/dist/web/webSettings.js +10 -2
  88. package/docs/managed-turn-and-session-runtime.md +10 -0
  89. package/docs/managed-turn-and-session-runtime.zh-CN.md +6 -0
  90. package/docs/release-workflow.md +46 -0
  91. package/docs/release-workflow.zh-CN.md +34 -0
  92. package/docs/roles-and-configuration.md +45 -1
  93. package/docs/roles-and-configuration.zh-CN.md +35 -0
  94. package/docs/storage-baseline.md +6 -1
  95. package/docs/storage-baseline.zh-CN.md +5 -1
  96. package/docs/testing/review-evaluation.md +35 -1
  97. package/docs/testing/review-evaluation.zh-CN.md +25 -1
  98. package/docs/testing/verification-levels.md +2 -2
  99. package/docs/testing/verification-levels.zh-CN.md +2 -2
  100. package/package.json +1 -1
  101. package/skills/yui-leader/VERSION +1 -1
  102. package/skills/yui-leader/references/execution.md +19 -8
  103. package/skills/yui-reviewer/SKILL.md +13 -2
  104. package/skills/yui-reviewer/VERSION +1 -1
@@ -1,17 +1,28 @@
1
1
  import { DOCK } from "../shared/geometry.js";
2
2
  import { iconSvg } from "../shared/icons.js";
3
- // The dock divider and the dock region: the Discussion / Live session tabs,
4
- // their actions, the discussion mount and the terminal pane, whose host,
5
- // target chips and connection state must survive re-renders.
3
+ // The dock divider and the dock region: one header row with the target menu
4
+ // (the only Role selector), the view tabs (icon-only until the dock is wide)
5
+ // and the dock actions; then the session page mount (chat or terminal, built
6
+ // by views/dock/session), the discussion mount and the preview.
6
7
  // The string keeps its indentation inside .app and has no trailing newline.
7
8
  export const DOCK_HTML = ` <div id="dock-divider" class="dock-divider" role="separator" tabindex="0" aria-orientation="vertical" aria-valuemin="${DOCK.min}" aria-valuemax="${DOCK.max}" aria-valuenow="${DOCK.initial}" aria-label="Resize session panel" data-i18n-aria-label="dock.resize" hidden><span aria-hidden="true"></span></div>
8
9
  <aside id="dock" class="dock" aria-label="Session" data-i18n-aria-label="dock.label" hidden>
9
10
  <header class="dock-head">
10
- <div class="seg" role="tablist" aria-label="Session view" data-i18n-aria-label="dock.label">
11
- <button id="dock-tab-conversation" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-conversation"><span data-i18n="conversation.title">Conversation</span></button>
12
- <button id="dock-tab-discussion" class="seg-btn" type="button" role="tab" aria-selected="true" aria-controls="dock-discussion">${iconSvg("chat")}<span data-i18n="dock.discussion">Discussion</span></button>
13
- <button id="dock-tab-session" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-session">${iconSvg("terminal")}<span data-i18n="dock.session">Live session</span></button>
14
- <button id="dock-tab-preview" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-preview" disabled><span data-i18n="preview.title">Preview</span></button>
11
+ <details id="dock-target" class="dock-target">
12
+ <summary class="dock-target-btn" aria-label="Conversation target" data-i18n-aria-label="dock.target">
13
+ <span id="dock-target-icon" class="dock-target-icon" aria-hidden="true">${iconSvg("broadcast")}</span>
14
+ <span id="dock-target-label" class="dock-target-label">Operator</span>${iconSvg("down")}
15
+ </summary>
16
+ <div class="dock-target-menu">
17
+ <div id="dock-target-options" class="dock-target-options" role="group" aria-label="Conversation target" data-i18n-aria-label="dock.target"></div>
18
+ <hr>
19
+ <button id="global-input-open" class="dock-target-item" type="button">${iconSvg("send")}<span data-i18n="global.openRole">Message another Role…</span></button>
20
+ </div>
21
+ </details>
22
+ <div class="seg seg-sm dock-tabs" role="tablist" aria-label="Session view" data-i18n-aria-label="dock.label">
23
+ <button id="dock-tab-conversation" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-conversation" aria-label="Conversation" data-i18n-aria-label="conversation.title" title="Conversation" data-i18n-title="conversation.title">${iconSvg("sparkle")}<span data-i18n="conversation.title">Conversation</span></button>
24
+ <button id="dock-tab-discussion" class="seg-btn" type="button" role="tab" aria-selected="true" aria-controls="dock-discussion" aria-label="Discussion" data-i18n-aria-label="dock.discussion" title="Discussion" data-i18n-title="dock.discussion">${iconSvg("chat")}<span data-i18n="dock.discussion">Discussion</span></button>
25
+ <button id="dock-tab-preview" class="seg-btn" type="button" role="tab" aria-selected="false" aria-controls="dock-preview" aria-label="Preview" data-i18n-aria-label="preview.title" title="Preview" data-i18n-title="preview.title" disabled>${iconSvg("eye")}<span data-i18n="preview.title">Preview</span></button>
15
26
  </div>
16
27
  <div class="dock-actions">
17
28
  <button id="dock-swap" class="icon-btn" type="button" aria-label="Swap with task details" data-i18n-aria-label="dock.swap" title="Swap with task details">${iconSvg("swap")}</button>
@@ -21,20 +32,4 @@ export const DOCK_HTML = ` <div id="dock-divider" class="dock-divider" role="
21
32
  <section id="dock-conversation" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-conversation" hidden></section>
22
33
  <section id="dock-discussion" class="dock-pane dock-discussion" role="tabpanel" aria-labelledby="dock-tab-discussion"></section>
23
34
  <section id="dock-preview" class="dock-pane dock-preview" role="tabpanel" aria-labelledby="dock-tab-preview" hidden></section>
24
- <section id="dock-session" class="dock-pane dock-session" role="tabpanel" aria-labelledby="dock-tab-session" hidden>
25
- <div class="session-bar">
26
- <div id="session-targets" class="session-targets" role="group" aria-label="Session target" data-i18n-aria-label="dock.target"></div>
27
- <span id="terminal-state" class="conn" data-state="idle"><i aria-hidden="true"></i><span data-i18n="terminal.idle">Not connected</span></span>
28
- </div>
29
- <div id="terminal-host" class="terminal-host"></div>
30
- <div id="terminal-empty" class="terminal-empty">
31
- ${iconSvg("terminal", "icon icon-xl")}
32
- <p class="terminal-empty-title" data-i18n="terminal.emptyTitle">Attach to a native Session</p>
33
- <p class="terminal-empty-text" data-i18n="terminal.emptyText">Pick a Role above. The panel shows the Session's current screen; attaching never starts or restarts an Agent.</p>
34
- </div>
35
- <footer class="session-foot">
36
- <span data-i18n="terminal.cliHint">Full terminal</span>
37
- <code id="terminal-cli">yui operator enter</code>
38
- </footer>
39
- </section>
40
35
  </aside>`;
@@ -1,8 +1,10 @@
1
+ import { BRAND_ICONS } from "../shared/brand.js";
1
2
  import { DEFAULT_DARK_THEME } from "../shared/themes.js";
2
3
  import { STYLESHEETS } from "../styles/index.js";
3
4
  import { DIALOGS_HTML } from "./dialogs.js";
4
5
  import { DOCK_HTML } from "./dock.js";
5
6
  import { SIDEBAR_HTML } from "./sidebar.js";
7
+ import { appRail } from "./appRail.js";
6
8
  // Static application frame. Everything inside #detail, #task-list and the dock
7
9
  // panes is rendered by the client modules; the shell only fixes the regions
8
10
  // and the controls whose identity must survive re-renders. Stylesheets are
@@ -17,8 +19,8 @@ export const DASHBOARD_HTML = `<!doctype html>
17
19
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
18
20
  <meta name="color-scheme" content="dark light">
19
21
  <meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
22
+ <link rel="icon" type="image/svg+xml" href="${BRAND_ICONS[DEFAULT_DARK_THEME.id]}">
20
23
  <script src="/assets/theme.js"></script>
21
- <link rel="icon" href="data:,">
22
24
  <title>Yui</title>
23
25
  <link rel="preload" href="/assets/fonts/inter-500.woff2" as="font" type="font/woff2" crossorigin>
24
26
  <link rel="preload" href="/assets/fonts/inter-600.woff2" as="font" type="font/woff2" crossorigin>
@@ -28,7 +30,9 @@ ${STYLESHEET_LINKS}
28
30
  <body>
29
31
  <a class="skip-link" href="#center" data-i18n="a11y.skip">Skip to task details</a>
30
32
  <div class="app">
33
+ ${appRail("workbench")}
31
34
  ${SIDEBAR_HTML}
35
+ <div id="sidebar-scrim" class="sidebar-scrim"></div>
32
36
  <main id="center" class="center" tabindex="-1">
33
37
  <div id="detail" class="detail"></div>
34
38
  </main>
@@ -36,6 +40,6 @@ ${DOCK_HTML}
36
40
  </div>
37
41
  ${DIALOGS_HTML}
38
42
  <div id="toast" class="toast" role="status" aria-live="polite"></div>
39
- <script type="module" src="/assets/app.js"></script>
43
+ <script type="module" src="/assets/app.js" blocking="render"></script>
40
44
  </body>
41
45
  </html>`;
@@ -1,18 +1,19 @@
1
1
  import { DEFAULT_DARK_THEME } from "../shared/themes.js";
2
+ import { BRAND_ICONS, brandGlyphSvg } from "../shared/brand.js";
2
3
  import { iconSvg } from "../shared/icons.js";
3
4
  // The only document served without a session. It carries no page token and
4
5
  // no Task data; the client module posts the password as same-origin JSON and
5
6
  // the server answers with a session cookie. CSP form-action 'none' means the
6
7
  // form can never submit natively (so a password cannot end up in a URL).
7
- const SHEETS = ["fonts", "tokens", "base", "ui/controls", "ui/forms", "views/login"];
8
+ const SHEETS = ["fonts", "tokens", "base", "motion", "ui/controls", "ui/forms", "views/login"];
8
9
  export const LOGIN_HTML = `<!doctype html>
9
10
  <html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
10
11
  <head>
11
12
  <meta charset="utf-8">
12
13
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
13
14
  <meta name="color-scheme" content="dark light">
15
+ <link rel="icon" type="image/svg+xml" href="${BRAND_ICONS[DEFAULT_DARK_THEME.id]}">
14
16
  <script src="/assets/theme.js"></script>
15
- <link rel="icon" href="data:,">
16
17
  <title>Sign in · Yui</title>
17
18
  <link rel="preload" href="/assets/fonts/inter-500.woff2" as="font" type="font/woff2" crossorigin>
18
19
  <link rel="preload" href="/assets/fonts/inter-600.woff2" as="font" type="font/woff2" crossorigin>
@@ -20,13 +21,13 @@ ${SHEETS.map(name => ` <link rel="stylesheet" href="/assets/css/${name}.css">`)
20
21
  </head>
21
22
  <body class="login-body">
22
23
  <main class="login-shell">
23
- <div class="login-top">
24
- <label class="login-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
25
- </div>
26
24
  <section class="login-card" aria-labelledby="login-title">
27
- <div class="login-brand">
28
- <span class="brand-mark" aria-hidden="true">結</span>
29
- <span class="brand-name">Yui</span>
25
+ <div class="login-top">
26
+ <div class="login-brand">
27
+ <span class="brand-mark" aria-hidden="true">${brandGlyphSvg()}</span>
28
+ <span class="brand-name">Yui</span>
29
+ </div>
30
+ <label class="login-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
30
31
  </div>
31
32
  <header class="login-heading">
32
33
  <h1 id="login-title" data-i18n="login.heading">Sign in to Yui</h1>
@@ -1,51 +1,63 @@
1
1
  import { STYLESHEETS } from "../styles/index.js";
2
2
  import { DEFAULT_DARK_THEME } from "../shared/themes.js";
3
+ import { BRAND_ICONS } from "../shared/brand.js";
3
4
  import { iconSvg } from "../shared/icons.js";
5
+ import { appRail } from "./appRail.js";
4
6
  import { MANAGE_SECTIONS } from "../shared/manageNav.js";
5
- // One frame and entry point for /models/* and /settings/*. The shared router
6
- // changes sections without unloading their controllers or drafts. Leaving
7
- // the management document still uses the browser's unsaved-draft prompt.
7
+ // One frame and entry point for /models/* and /settings/*. The frame is the
8
+ // workbench's own: the global rail, a collapsible list (here the current
9
+ // area's sections), and the center column under the shared top bar. Narrow
10
+ // screens open the rail and the list together as a drawer, exactly as on the
11
+ // workbench. The shared router changes sections without unloading their
12
+ // controllers or drafts. Leaving the management document still uses the
13
+ // browser's unsaved-draft prompt.
8
14
  const escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
9
15
  const sectionLink = (item) => `<a class="manage-link" href="/${item.section === "connection" ? "settings" : item.page}/${item.section}" data-page="${item.page}" data-section="${item.section}"><span data-i18n="${item.key}">${escapeHtml(item.label)}</span><i class="manage-dirty" aria-hidden="true"></i></a>`;
10
- const area = (name, icon, key, label) => `
16
+ const area = (name, key, label) => `
11
17
  <div class="manage-area" data-area-group="${name}">
12
- <a class="manage-primary" href="/${name}" data-area="${name}">${iconSvg(icon)}<span data-i18n="${key}">${label}</span></a>
13
18
  <div class="manage-sections" role="group" aria-label="${label}" data-i18n-aria-label="${key}">
14
19
  ${MANAGE_SECTIONS[name].map(sectionLink).join("\n ")}
15
20
  </div>
16
21
  </div>`;
17
- const RAIL = `<aside class="manage-rail">
18
- <header class="manage-rail-head">
19
- <a class="brand" href="/" title="Workbench" data-i18n-title="manage.workbench">
20
- <span class="brand-mark" aria-hidden="true">結</span><span class="brand-name">Yui</span>
21
- </a>
22
- <label class="manage-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
22
+ const RAIL = `<aside class="manage-rail sidebar" aria-label="Sections" data-i18n-aria-label="manage.sections">
23
+ <header class="side-head">
24
+ <h2 id="manage-area-title" class="side-title"></h2>
25
+ <button id="sidebar-collapse" class="icon-btn side-collapse" type="button" aria-label="Collapse sections" data-i18n-aria-label="manage.collapseSections" title="Collapse sections · [" data-i18n-title="manage.collapseSections">${iconSvg("sidebar")}</button>
23
26
  </header>
24
- <nav class="manage-nav" aria-label="Main navigation" data-i18n-aria-label="manage.nav">
25
- <div class="manage-area" data-area-group="workbench">
26
- <a class="manage-primary" href="/" data-area="workbench">${iconSvg("inbox")}<span data-i18n="manage.workbench">Workbench</span></a>
27
- </div>${area("models", "layers", "models.title", "Models")}${area("settings", "settings", "settings.title", "Settings")}
27
+ <nav class="manage-nav" aria-label="Sections" data-i18n-aria-label="manage.sections">${area("models", "models.title", "Models")}${area("settings", "settings.title", "Settings")}
28
28
  </nav>
29
29
  </aside>`;
30
+ const TOP_BAR = `<header class="top-bar">
31
+ <button class="icon-btn side-expand" type="button" data-sidebar-toggle="true" aria-expanded="false" aria-label="Show sections" data-i18n-aria-label="manage.showSections" title="Show sections · [" data-i18n-title="manage.showSections"><span class="i-sidebar">${iconSvg("sidebar")}</span><span class="i-menu">${iconSvg("menu")}</span></button>
32
+ <nav class="crumbs" aria-label="Location" data-i18n-aria-label="crumbs.label"><span id="manage-crumb-area" class="crumb crumb-static"></span><span class="crumb-sep" aria-hidden="true">/</span><span id="manage-crumb-section" class="crumb-current"></span></nav>
33
+ <span class="spacer"></span>
34
+ <label class="top-locale"><span class="sr-only" data-i18n="settings.language">Language</span><select id="locale-select"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
35
+ </header>`;
30
36
  // One management document. `main` is the page's reading column markup.
31
37
  export function manageDocument(options) {
32
38
  return `<!doctype html>
33
39
  <html lang="en" data-theme="${DEFAULT_DARK_THEME.id}">
34
40
  <head>
35
- <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
41
+ <meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
36
42
  <meta name="yui-web-token" content="__YUI_WEB_TOKEN__">
43
+ <link rel="icon" type="image/svg+xml" href="${BRAND_ICONS[DEFAULT_DARK_THEME.id]}">
37
44
  <script src="/assets/theme.js"></script>
38
- <link rel="icon" href="data:,"><title>Yui · ${options.title}</title>
45
+ <title>Yui · ${options.title}</title>
39
46
  ${STYLESHEETS.map(s => `<link rel="stylesheet" href="/assets/css/${s.name}.css">`).join("\n ")}
40
47
  </head>
41
48
  <body class="manage-body ${options.page}-body">
42
- <div class="manage-app" data-page="${options.page}" hidden>
49
+ <div class="app manage-app" data-page="${options.page}" hidden>
50
+ ${appRail(options.page === "models" ? "models" : "settings")}
43
51
  ${RAIL}
44
- <main class="manage-main" id="manage-main">
52
+ <div id="sidebar-scrim" class="sidebar-scrim"></div>
53
+ <main id="manage-scroll" class="center manage-center" tabindex="-1">
54
+ ${TOP_BAR}
55
+ <div class="manage-main" id="manage-main">
45
56
  ${options.main}
57
+ </div>
46
58
  </main>
47
59
  </div>
48
- <script type="module" src="/assets/js/app/manage.js"></script>
60
+ <script type="module" src="/assets/js/app/manage.js" blocking="render"></script>
49
61
  </body></html>`;
50
62
  }
51
63
  // The common section heading: title and one explanatory line.
@@ -10,7 +10,7 @@ export const SETTINGS_MAIN = `
10
10
  <fieldset class="field"><legend data-i18n="settings.theme">Theme</legend><div id="theme-options" class="theme-options"></div></fieldset>
11
11
  <div class="settings-stack">
12
12
  <label class="field"><span data-i18n="settings.language">Language</span><select id="settings-locale"><option value="en">English</option><option value="zh-CN">简体中文</option></select></label>
13
- <label class="field"><span data-i18n="settings.accessMode">Default session access</span><select id="access-mode"><option value="native" data-i18n="settings.native">Native terminal</option><option value="structured" data-i18n="settings.structured">Structured view</option></select></label>
13
+ <label class="field"><span data-i18n="settings.accessMode">Default session access</span><select id="access-mode"><option value="structured" data-i18n="settings.structured">Structured view</option><option value="native" data-i18n="settings.native">Native terminal</option></select></label>
14
14
  <p data-i18n="settings.accessHelp">Preference for subsequent session selection where supported. It does not switch a live Session.</p>
15
15
  <button id="reset-layout" type="button" class="btn" data-i18n="settings.resetLayout">Reset saved workspace layout</button>
16
16
  <p id="browser-receipt" role="status"></p>
@@ -6,24 +6,27 @@ import { iconSvg } from "../shared/icons.js";
6
6
  // The string keeps its indentation inside .app and has no trailing newline.
7
7
  export const SIDEBAR_HTML = ` <aside class="sidebar" aria-label="Tasks" data-i18n-aria-label="sidebar.label">
8
8
  <header class="side-head">
9
- <div class="brand">
10
- <span class="brand-mark" aria-hidden="true">結</span>
11
- <span class="brand-name">Yui</span>
12
- </div>
9
+ <h2 class="side-title" data-i18n="sidebar.label">Tasks</h2>
13
10
  <span class="sync" id="sync-state" title="Last sync" data-i18n-title="sync.label">
14
11
  <i aria-hidden="true"></i><time id="last-sync">—</time>
15
12
  </span>
16
13
  <button id="refresh" class="icon-btn" type="button" aria-label="Refresh" data-i18n-aria-label="actions.refresh" title="Refresh · R">${iconSvg("refresh")}</button>
14
+ <button id="sidebar-collapse" class="icon-btn side-collapse" type="button" aria-label="Collapse task list" data-i18n-aria-label="sidebar.collapse" title="Collapse task list · [" data-i18n-title="sidebar.collapse">${iconSvg("sidebar")}</button>
17
15
  </header>
18
- <button id="create-task" class="btn btn-primary" type="button" data-i18n="create.title">New task</button>
19
- <label class="search-field">
20
- ${iconSvg("search")}
21
- <span class="sr-only" data-i18n="search.label">Search tasks</span>
22
- <input id="search" type="search" autocomplete="off" spellcheck="false" placeholder="Search tasks" data-i18n-placeholder="search.placeholder">
23
- <kbd>/</kbd>
24
- </label>
25
- <button id="body-search-open" class="foot-btn" type="button">${iconSvg("search")}<span data-i18n="bodySearch.title">Search task contents</span></button>
26
- <div id="attention-bar" class="attention-bar" aria-live="polite"></div>
16
+ <div class="side-tools">
17
+ <button id="create-task" class="btn btn-primary side-create" type="button">${iconSvg("plus")}<span data-i18n="create.title">New task</span></button>
18
+ <div class="side-search">
19
+ <label class="search-field">
20
+ ${iconSvg("search")}
21
+ <span class="sr-only" data-i18n="search.label">Search tasks</span>
22
+ <input id="search" type="search" autocomplete="off" spellcheck="false" placeholder="Search tasks" data-i18n-placeholder="search.placeholder">
23
+ <kbd>/</kbd>
24
+ </label>
25
+ <button id="body-search-open" class="icon-btn side-search-body" type="button" aria-label="Search task contents" data-i18n-aria-label="bodySearch.title" title="Search task contents" data-i18n-title="bodySearch.title">${iconSvg("textSearch")}</button>
26
+ </div>
27
+ <div id="catalog-source" class="seg seg-sm source-seg" role="radiogroup" aria-label="Task category" data-i18n-aria-label="jobs.taskCategory"></div>
28
+ <div id="attention-bar" class="attention-bar" aria-live="polite"></div>
29
+ </div>
27
30
  <div id="status-filters" class="status-tabs" role="group" aria-label="Filter by status" data-i18n-aria-label="filters.label"></div>
28
31
  <div id="task-list" class="task-list" aria-label="Tasks" data-i18n-aria-label="sidebar.label" aria-live="polite"></div>
29
32
  <div id="catalog-controls" class="pager" aria-live="polite">
@@ -32,15 +35,5 @@ export const SIDEBAR_HTML = ` <aside class="sidebar" aria-label="Tasks" data-
32
35
  <button id="catalog-next" class="pager-btn" type="button" data-i18n="catalog.next" disabled>Next page</button>
33
36
  <button id="catalog-attention-reset" class="pager-clear" type="button" data-i18n="catalog.clearAttention" hidden>Clear attention filter</button>
34
37
  </div>
35
- <footer class="side-foot">
36
- <button id="operator-terminal" class="foot-btn" type="button" title="Operator session · O">
37
- ${iconSvg("terminal")}<span data-i18n="actions.operator">Operator</span><kbd>O</kbd>
38
- </button>
39
- <button id="global-input-open" class="foot-btn" type="button" title="Global input" data-i18n-title="global.open">
40
- ${iconSvg("broadcast")}<span data-i18n="global.open">Global input</span>
41
- </button>
42
- <a id="models-open" class="icon-btn" href="/models" aria-label="Models" data-i18n-aria-label="models.title" title="Models" data-i18n-title="models.title">${iconSvg("layers")}</a>
43
- <button id="settings-open" class="icon-btn" type="button" aria-label="Settings" data-i18n-aria-label="settings.title" title="Settings">${iconSvg("settings")}</button>
44
- </footer>
45
38
  </aside>
46
39
  <div id="sidebar-divider" class="sidebar-divider" role="separator" tabindex="0" aria-orientation="vertical" aria-valuemin="${SIDEBAR.min}" aria-valuemax="${SIDEBAR.max}" aria-valuenow="${SIDEBAR.compact}" aria-label="Resize task list" data-i18n-aria-label="sidebar.resize" title="Drag to resize · double-click to reset" data-i18n-title="sidebar.resizeHint"><span aria-hidden="true"></span></div>`;
@@ -28,7 +28,7 @@ input:disabled,select:disabled,textarea:disabled{opacity:.55}
28
28
  ::-webkit-scrollbar-track{background:transparent}
29
29
  [hidden]{display:none!important}
30
30
  .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
31
- .skip-link{position:fixed;left:12px;top:-60px;z-index:100;padding:9px 14px;border-radius:var(--r-sm);background:var(--accent);color:var(--accent-ink);text-decoration:none;font-weight:600;transition:top var(--t-fast)}
31
+ .skip-link{position:fixed;left:12px;top:-60px;z-index:var(--z-skip);padding:9px 14px;border-radius:var(--r-sm);background:var(--accent);color:var(--accent-ink);text-decoration:none;font-weight:600;transition:top var(--t-fast)}
32
32
  .skip-link:focus{top:12px}
33
33
  .icon{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round}
34
34
  .icon-sm{width:14px;height:14px}
@@ -3,6 +3,7 @@ import { BASE_STYLES } from "./base.js";
3
3
  import { RESPONSIVE_STYLES } from "./layout/responsive.js";
4
4
  import { WORKSPACE_STYLES } from "./layout/workspace.js";
5
5
  import { MARKDOWN_STYLES } from "./markdown.js";
6
+ import { MOTION_STYLES } from "./motion.js";
6
7
  import { TOKEN_STYLES } from "./tokens.js";
7
8
  import { CONTROL_STYLES } from "./ui/controls.js";
8
9
  import { FORM_STYLES } from "./ui/forms.js";
@@ -29,6 +30,7 @@ export const STYLESHEETS = Object.freeze([
29
30
  { name: "fonts", body: FONT_FACE_STYLES },
30
31
  { name: "tokens", body: TOKEN_STYLES },
31
32
  { name: "base", body: BASE_STYLES },
33
+ { name: "motion", body: MOTION_STYLES },
32
34
  { name: "layout/workspace", body: WORKSPACE_STYLES },
33
35
  { name: "ui/controls", body: CONTROL_STYLES },
34
36
  { name: "ui/surfaces", body: SURFACE_STYLES },
@@ -1,38 +1,83 @@
1
- import { BREAKPOINTS, SIDEBAR } from "../../shared/geometry.js";
1
+ import { BREAKPOINTS, DOCK_FOCUS_MAX, SIDEBAR } from "../../shared/geometry.js";
2
2
  /*
3
3
  * RESPONSIVE — breakpoints only, from shared/geometry.ts.
4
4
  * ≤ wide compact sidebar default and a three-column metric strip
5
- * ≤ narrow master–detail: task list OR task page; the dock becomes a
6
- * full-screen sheet over the task page
5
+ * ≤ focus an open dock moves the task list out of the grid so the task
6
+ * column keeps its minimum width; it opens as a drawer instead
7
+ * ≤ narrow the workspace takes the screen; the rail and the list open
8
+ * together as one drawer, and the dock becomes a full-screen sheet
7
9
  * ≤ small phone layout for page content
8
- * Sidebar container queries adapt its footer to the width the user chose.
10
+ *
11
+ * Motion follows the native sheet pattern (Vaul, Radix Sheet, iOS): drawers
12
+ * and sheets stay rendered off screen and move only by transform, so opening
13
+ * and closing both animate on the compositor; the scrim only fades. A drag
14
+ * (layout/sidebarNav) moves the panels directly and the release continues
15
+ * from where the finger left them.
9
16
  */
10
17
  export const RESPONSIVE_STYLES = `
11
- @container sidebar (max-width:299px){
12
- .foot-btn kbd{display:none}
13
- }
14
- @container sidebar (max-width:259px){
15
- .foot-btn{gap:0;padding:0 9px}
16
- .foot-btn span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
17
- }
18
18
  @media(max-width:${BREAKPOINTS.wide}px){
19
19
  :root{--sidebar-w:${SIDEBAR.compact}px}
20
20
  .metric-strip{grid-template-columns:repeat(3,minmax(0,1fr))}
21
21
  }
22
+ @media(min-width:${BREAKPOINTS.narrow + 1}px) and (max-width:${DOCK_FOCUS_MAX}px){
23
+ body.dock-open .app{grid-template-areas:"rail center divider dock";grid-template-columns:var(--rail-w) minmax(0,1fr) var(--divider-w) var(--dock-w)}
24
+ body.dock-open.dock-center .app{grid-template-areas:"rail dock divider center";grid-template-columns:var(--rail-w) var(--dock-w) var(--divider-w) minmax(0,1fr)}
25
+ body.dock-open .sidebar-divider{display:none}
26
+ body.dock-open .side-expand{display:inline-grid}
27
+ /* The list slides out from under the rail. */
28
+ body.dock-open .app-rail{position:relative;z-index:var(--z-rail-over)}
29
+ body.dock-open.sidebar-collapsed .app .sidebar{display:flex}
30
+ body.dock-open .app .sidebar{--drawer-w:min(340px,calc(100vw - var(--rail-w) - 24px));display:flex;position:fixed;top:0;bottom:0;left:var(--rail-w);z-index:var(--z-drawer);width:var(--drawer-w);border-right:1px solid var(--line-2);box-shadow:var(--shadow-2);transform:translate3d(calc(-100% - 32px),0,0);visibility:hidden;transition:transform var(--t-sheet-out) var(--ease-sheet),visibility 0s linear var(--t-sheet-out)}
31
+ }
22
32
  @media(max-width:${BREAKPOINTS.narrow}px){
33
+ :root{--drawer-w:min(320px,calc(100vw - var(--rail-w) - 48px));--drawer-off:calc(-1 * (var(--rail-w) + var(--drawer-w)) - 32px)}
23
34
  .app,body.dock-open .app,body.dock-open.dock-center .app{grid-template-areas:"main";grid-template-columns:minmax(0,1fr)}
24
- .sidebar{grid-area:main;border-right:0}
25
- .center{grid-area:main;display:none}
26
- body.task-open .sidebar{display:none}
27
- body.task-open .center{display:block}
28
- .back-btn{display:inline-grid}
29
- .crumbs .crumb,.crumbs .crumb-sep{display:none}
35
+ .center{grid-area:main}
36
+ /* One drawer: the rail and the list, side by side, moving together. */
37
+ .app .app-rail,.app .sidebar{display:flex;position:fixed;top:0;bottom:0;z-index:var(--z-drawer);padding-top:env(safe-area-inset-top,0px);transform:translate3d(var(--drawer-off),0,0);visibility:hidden;transition:transform var(--t-sheet-out) var(--ease-sheet),visibility 0s linear var(--t-sheet-out);touch-action:pan-y}
38
+ body.sidebar-collapsed .app .sidebar{display:flex}
39
+ .app .app-rail{left:0;width:var(--rail-w);padding-top:calc(10px + env(safe-area-inset-top,0px))}
40
+ .app .sidebar{left:var(--rail-w);width:var(--drawer-w);border-right:1px solid var(--line-2);border-radius:0 var(--r-lg) var(--r-lg) 0;box-shadow:var(--shadow-2)}
41
+ .icon-btn.side-expand{display:inline-grid}
42
+ .side-expand .i-sidebar{display:none}
43
+ .side-expand .i-menu{display:contents}
44
+ /* Top bar: the current place is the title. */
45
+ .top-bar{padding:0 12px 0 8px}
46
+ .crumbs>span.crumb,.crumbs>span.crumb+.crumb-sep{display:none}
47
+ .crumbs span.crumb-current{font-size:var(--fs-lg)}
30
48
  .dock-divider,.sidebar-divider{display:none}
31
- .dock{position:fixed;inset:0;z-index:60;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}
49
+ .dock{position:fixed;inset:0;z-index:var(--z-sheet);padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px);animation:m-sheet-up var(--t-sheet) var(--ease-sheet)}
32
50
  #dock-swap{display:none}
33
51
  .dock-toggle span{display:none}
34
52
  .dock-toggle{width:32px;padding:0}
35
53
  }
54
+ /* Open drawer (sidebarNav sets body.sidebar-drawer only where the list
55
+ cannot sit inline). Opening eases in a little slower than closing. */
56
+ body.sidebar-drawer .app .sidebar,body.sidebar-drawer .app .app-rail{transform:translate3d(0,0,0);visibility:visible;transition:transform var(--t-sheet) var(--ease-sheet),visibility 0s}
57
+ body.sidebar-drawer .app .sidebar-divider{display:none}
58
+ .sidebar-scrim{position:fixed;inset:0;z-index:var(--z-scrim);background:rgb(12 10 8 / .4);opacity:0;visibility:hidden;pointer-events:none;transition:opacity var(--t-sheet-out) var(--ease-sheet),visibility 0s linear var(--t-sheet-out)}
59
+ body.sidebar-drawer .sidebar-scrim{opacity:1;visibility:visible;pointer-events:auto;transition:opacity var(--t-sheet) var(--ease-sheet),visibility 0s}
60
+ /* While a finger drags the drawer, it follows directly. */
61
+ body.drawer-dragging .app .sidebar,body.drawer-dragging .app .app-rail,body.drawer-dragging .sidebar-scrim{transition:none}
62
+ @media(prefers-reduced-motion:reduce){
63
+ .app .sidebar,.app .app-rail,.sidebar-scrim,body.sidebar-drawer .app .sidebar,body.sidebar-drawer .app .app-rail,body.sidebar-drawer .sidebar-scrim{transition:none}
64
+ .dock{animation:none}
65
+ }
66
+ /* Touch screens: controls reach a comfortable tap size without changing the
67
+ desktop density. */
68
+ @media(pointer:coarse){
69
+ .btn,.icon-btn,.dock-toggle{min-height:38px}
70
+ .icon-btn{min-width:38px}
71
+ .dock-toggle{min-width:38px}
72
+ .seg-btn,.seg-sm .seg-btn{height:36px}
73
+ .cv-pill{height:36px}
74
+ .cv-link{min-height:32px}
75
+ .chip-tab{height:34px}
76
+ .btn-link{min-height:32px;padding-top:6px;padding-bottom:6px}
77
+ .cv-activity>summary{min-height:32px}
78
+ .cv-sub>summary{padding:8px 0}
79
+ .task-row{min-height:48px}
80
+ }
36
81
  @media(max-width:${BREAKPOINTS.small}px){
37
82
  .metric-strip{grid-template-columns:repeat(2,minmax(0,1fr))}
38
83
  .page-head{padding-top:24px;flex-wrap:wrap}
@@ -46,7 +91,6 @@ export const RESPONSIVE_STYLES = `
46
91
  .now-card>.card-body{padding:14px 14px 14px 17px}
47
92
  .kv{grid-template-columns:minmax(0,1fr)}
48
93
  .kv dt{margin-top:4px}
49
- .foot-btn kbd{display:none}
50
94
  .lazy-meta{display:none}
51
95
  .lazy-body{padding-left:16px}
52
96
  }
@@ -1,18 +1,58 @@
1
+ import { RAIL_WIDTH } from "../../shared/geometry.js";
1
2
  /*
2
- * WORKSPACE — the three-region application frame.
3
- * sidebar | center (task details) | divider | dock (session)
4
- * body.dock-open shows the dock; body.dock-center swaps the dock into the
5
- * middle so the order becomes sidebar | dock | divider | center. The sidebar's
6
- * own divider overlays its right edge, so it adds no grid column; --sidebar-w
7
- * is the default width and a stored preference overrides it inline. Region
3
+ * WORKSPACE — the application frame.
4
+ * rail | sidebar | center (task details) | divider | dock (session)
5
+ * The rail is the global Workbench / Models / Settings navigation, shared with
6
+ * the management pages. body.dock-open shows the dock; body.dock-center swaps
7
+ * the dock into the middle so the order becomes rail | sidebar | dock |
8
+ * divider | center. body.sidebar-collapsed folds the task list away (the page
9
+ * header then offers .side-expand to bring it back). The sidebar's own
10
+ * divider overlays its right edge, so it adds no grid column; --sidebar-w is
11
+ * the default width and a stored preference overrides it inline. Region
8
12
  * contents are styled by the view stylesheets.
9
13
  */
10
14
  export const WORKSPACE_STYLES = `
15
+ :root{--rail-w:${RAIL_WIDTH}px}
11
16
  .app{display:grid;height:100vh;height:100dvh;grid-template-rows:minmax(0,1fr);
12
- grid-template-areas:"sidebar center divider dock";
13
- grid-template-columns:var(--sidebar-w) minmax(0,1fr) 0 0}
14
- body.dock-open .app{grid-template-columns:var(--sidebar-w) minmax(0,1fr) var(--divider-w) var(--dock-w)}
15
- body.dock-open.dock-center .app{grid-template-areas:"sidebar dock divider center";grid-template-columns:var(--sidebar-w) var(--dock-w) var(--divider-w) minmax(0,1fr)}
17
+ grid-template-areas:"rail sidebar center divider dock";
18
+ grid-template-columns:var(--rail-w) var(--sidebar-w) minmax(0,1fr) 0 0}
19
+ body.dock-open .app{grid-template-columns:var(--rail-w) var(--sidebar-w) minmax(0,1fr) var(--divider-w) var(--dock-w)}
20
+ body.dock-open.dock-center .app{grid-template-areas:"rail sidebar dock divider center";grid-template-columns:var(--rail-w) var(--sidebar-w) var(--dock-w) var(--divider-w) minmax(0,1fr)}
21
+ body.sidebar-collapsed{--sidebar-w:0px}
22
+ body.sidebar-collapsed:not(.sidebar-drawer) .sidebar,body.sidebar-collapsed .sidebar-divider{display:none}
23
+
24
+ /* Global rail: brand, then the three areas as icon-over-label tiles. */
25
+ .app-rail{grid-area:rail;display:flex;flex-direction:column;align-items:center;gap:6px;min-height:0;padding:10px 0 12px;background:var(--surface);border-right:1px solid var(--line);overflow-y:auto}
26
+ .rail-brand{display:grid;place-items:center;width:40px;height:40px;margin-bottom:6px;border-radius:var(--r-md);text-decoration:none}
27
+ .rail-brand .brand-mark{display:grid;place-items:center}
28
+ .rail-brand:hover .brand-mark{box-shadow:0 0 0 3px var(--accent-soft)}
29
+ .rail-items{display:flex;flex-direction:column;align-items:center;gap:4px}
30
+ .rail-item{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:3px;width:48px;min-height:50px;padding:6px 2px;border-radius:var(--r-md);color:var(--ink-3);font-size:10.5px;font-weight:550;line-height:1.15;text-align:center;text-decoration:none;transition:background var(--t-fast),color var(--t-fast),transform var(--t-fast) var(--ease)}
31
+ .rail-item .icon{width:18px;height:18px;flex:none}
32
+ .rail-item span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
33
+ .rail-item:hover{background:var(--surface-3);color:var(--ink)}
34
+ .rail-item[aria-current="page"]{background:var(--accent-soft);color:var(--accent)}
35
+
36
+ /* Task-list switch in the page header: only while the list is out of view. */
37
+ .icon-btn.side-expand{display:none}
38
+ .side-expand .i-menu{display:none}
39
+ .side-expand>span{display:contents}
40
+ body.sidebar-collapsed .side-expand{display:inline-grid}
41
+ .side-title{flex:1;min-width:0;font-size:var(--fs-md);font-weight:650;letter-spacing:-.01em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
42
+ .sidebar-scrim[hidden]{display:none}
43
+
44
+ /* Top bar: the first row of the center column on every page. Its height and
45
+ rule line up with the task-list head and the dock head, so the frame reads
46
+ as one header row: list switch, location, then the page's actions. */
47
+ .top-bar{position:sticky;top:0;z-index:var(--z-sticky);display:flex;align-items:center;gap:6px;height:var(--header-h);padding:0 16px 0 12px;border-bottom:1px solid var(--line);background:var(--canvas)}
48
+ .top-title{min-width:0;overflow:hidden;color:var(--ink);font-size:var(--fs-md);font-weight:600;text-overflow:ellipsis;white-space:nowrap}
49
+ .crumbs{display:flex;align-items:center;gap:4px;min-width:0;font-size:var(--fs-sm);white-space:nowrap}
50
+ .crumb,.crumb-sep{flex:none}
51
+ .crumb{padding:3px 6px;border:0;border-radius:var(--r-xs);background:transparent;color:var(--ink-3);font-size:var(--fs-sm)}
52
+ button.crumb:hover{color:var(--ink);background:var(--surface-3)}
53
+ .crumb-sep{color:var(--ink-4)}
54
+ .crumb-current{min-width:0;overflow:hidden;padding:3px 6px;color:var(--ink);font-weight:600;text-overflow:ellipsis}
55
+ code.crumb-current{font-weight:500;font-size:var(--fs-xs);color:var(--ink-2)}
16
56
 
17
57
  /* Resize dividers: a wide invisible hit area around a 1px rule that lights up
18
58
  on hover, focus and while dragging. */
@@ -25,16 +65,13 @@ body.sidebar-resizing,body.dock-resizing{cursor:col-resize;user-select:none}
25
65
 
26
66
  /* Sidebar */
27
67
  .sidebar{grid-area:sidebar;display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--surface);border-right:1px solid var(--line);container:sidebar/inline-size}
28
- .sidebar-divider{grid-area:sidebar;justify-self:end;z-index:20;width:var(--divider-w);margin-right:-5px}
68
+ .sidebar-divider{grid-area:sidebar;justify-self:end;z-index:var(--z-header);width:var(--divider-w);margin-right:-5px}
29
69
  .sidebar-divider span{left:3px;background:transparent}
30
70
  .side-head{flex:none;display:flex;align-items:center;gap:10px;height:var(--header-h);padding:0 12px 0 16px;border-bottom:1px solid var(--line)}
31
- .brand{display:flex;align-items:center;gap:10px;flex:1;min-width:0}
32
- .attention-bar{flex:none;margin:10px 12px 0}
33
- .status-tabs{flex:none;margin:10px 0 0;padding:0 12px}
71
+ .status-tabs{flex:none;margin:4px 0 0;padding:0 12px}
34
72
  .task-list{flex:1;min-height:0;overflow-y:auto;padding:4px 8px 12px;scrollbar-gutter:stable}
35
73
  .pager{flex:none;display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:6px;padding:8px 12px;border-top:1px solid var(--line)}
36
74
  .pager .pager-clear{grid-column:1/-1}
37
- .side-foot{flex:none;display:flex;align-items:center;gap:4px;padding:10px;border-top:1px solid var(--line)}
38
75
 
39
76
  /* Center */
40
77
  .center{grid-area:center;min-width:0;min-height:0;overflow-y:auto;scrollbar-gutter:stable;background:var(--canvas)}
@@ -42,7 +79,7 @@ body.sidebar-resizing,body.dock-resizing{cursor:col-resize;user-select:none}
42
79
  .detail{min-height:100%}
43
80
 
44
81
  /* Dock */
45
- .dock-divider{grid-area:divider;background:var(--canvas);z-index:5}
82
+ .dock-divider{grid-area:divider;background:var(--canvas);z-index:var(--z-raised)}
46
83
  .dock-divider span{left:4px;background:var(--line-2)}
47
84
  .dock{grid-area:dock;display:flex;flex-direction:column;min-width:0;min-height:0;background:var(--surface)}
48
85
  body.dock-open:not(.dock-center) .dock{border-left:0}
@@ -0,0 +1,51 @@
1
+ import { BREAKPOINTS } from "../shared/geometry.js";
2
+ import { motionKeyframes } from "../shared/motion.js";
3
+ /*
4
+ * MOTION — the shared animation library. Timings, curves and layers are the
5
+ * tokens generated from shared/motion.ts; this sheet holds the keyframes and
6
+ * the component motion that is the same everywhere:
7
+ * dialogs scale + fade in, quicker fade out; the backdrop fades
8
+ * (@starting-style + allow-discrete, as Radix Dialog does)
9
+ * popovers <details> menus pop in from their anchor
10
+ * press buttons settle slightly under the pointer
11
+ * pages cross-document View Transitions between Workbench, Models
12
+ * and Settings; the rail stays put
13
+ * layout same-document View Transitions for column changes (the
14
+ * client wraps sidebar collapse and dock open/close)
15
+ * Everything honours prefers-reduced-motion (base.ts turns it all off).
16
+ */
17
+ export const MOTION_STYLES = `
18
+ ${motionKeyframes()}
19
+ .dialog{opacity:0;transform:scale(.96);transition:opacity var(--t-exit) var(--ease-exit),transform var(--t-exit) var(--ease-exit),overlay var(--t-exit) allow-discrete,display var(--t-exit) allow-discrete}
20
+ .dialog[open]{opacity:1;transform:none;transition-duration:var(--t-base);transition-timing-function:var(--ease-standard)}
21
+ .dialog::backdrop{opacity:0;transition:opacity var(--t-exit) var(--ease-exit),overlay var(--t-exit) allow-discrete,display var(--t-exit) allow-discrete}
22
+ .dialog[open]::backdrop{opacity:1;transition-duration:var(--t-base)}
23
+ @starting-style{.dialog[open]{opacity:0;transform:scale(.96)}.dialog[open]::backdrop{opacity:0}}
24
+ details[open]>.cv-popbody,details[open]>.dock-target-menu{animation:m-pop var(--t-base) var(--ease-standard)}
25
+ .cv-attach>.cv-popbody,.cv-model>.cv-popbody{transform-origin:bottom left}
26
+ .cv-model>.cv-popbody{transform-origin:bottom right}
27
+ .cv-menu>.cv-popbody{transform-origin:top right}
28
+ .dock-target-menu{transform-origin:top left}
29
+ .icon-btn:active:not(:disabled),.rail-item:active{transform:scale(.94)}
30
+ /* Workbench ⇄ Models ⇄ Settings are separate documents: the browser
31
+ cross-fades them (no blank frame) and the rail stays put between them. */
32
+ @view-transition{navigation:auto}
33
+ ::view-transition-old(root),::view-transition-new(root){animation-duration:var(--t-page);animation-timing-function:var(--ease-sheet)}
34
+ @media(prefers-reduced-motion:reduce){::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){animation:none!important}}
35
+ /* Page content entering after an explicit navigation (client/lib/motion). */
36
+ .m-enter{animation:m-rise var(--t-base) var(--ease-standard)}
37
+ @media(min-width:${BREAKPOINTS.narrow + 1}px){
38
+ .app-rail{view-transition-name:app-rail}
39
+ /* Column changes: client/lib/motion sets .vt-layout for the transition
40
+ only. Each column glides between its old and new box showing its new
41
+ content (no double image); a column that appears or leaves fades. */
42
+ .vt-layout .sidebar{view-transition-name:sidebar}
43
+ .vt-layout .center{view-transition-name:center}
44
+ .vt-layout .dock{view-transition-name:dock}
45
+ ::view-transition-group(sidebar),::view-transition-group(center),::view-transition-group(dock){animation-duration:var(--t-layout);animation-timing-function:var(--ease-sheet);overflow:clip}
46
+ ::view-transition-old(sidebar),::view-transition-new(sidebar),::view-transition-old(center),::view-transition-new(center),::view-transition-old(dock),::view-transition-new(dock){width:100%;height:100%;object-fit:none;object-position:0 0;animation:none}
47
+ ::view-transition-old(sidebar),::view-transition-old(center),::view-transition-old(dock){opacity:0}
48
+ ::view-transition-old(sidebar):only-child,::view-transition-old(dock):only-child{opacity:1;animation:m-fade var(--t-exit) var(--ease-exit) reverse both}
49
+ ::view-transition-new(sidebar):only-child,::view-transition-new(dock):only-child{animation:m-fade var(--t-layout) var(--ease-standard) both}
50
+ }
51
+ `;