diff --git a/apps/presentation/dashboard/smoke/goal-workspace-craft-browser-smoke.mjs b/apps/presentation/dashboard/smoke/goal-workspace-craft-browser-smoke.mjs
index ca199f23b3..bdacc81b26 100644
--- a/apps/presentation/dashboard/smoke/goal-workspace-craft-browser-smoke.mjs
+++ b/apps/presentation/dashboard/smoke/goal-workspace-craft-browser-smoke.mjs
@@ -33,6 +33,15 @@ try {
api.transformStatus = fixture => {
transform(fixture);
fixture.run_history.goals.find(goal => goal.id === showcaseGoalId).display_name = longTitle;
+ if (state.quiet) {
+ const queue = fixture.attention_queue.items.find(item => item.goal_id === showcaseGoalId);
+ Object.assign(queue.agent_todos, { advancement_done_count: 0, done_count: 0, deferred_count: 0, open_count: 0, total_count: 0, items: [], deferred_items: [] });
+ Object.assign(queue.project_asset.agent_todos, { advancement_done_count: 0, done: 0, deferred_count: 0, open: 0, total: 0, items: [], recent_completed_advancement_items: [] });
+ Object.assign(queue.user_todos, { open_count: 0, total_count: 0, items: [] });
+ for (const agent of fixture.agent_management_projection.agents) {
+ if (agent.current_todo?.goal_id === showcaseGoalId) agent.current_todo = null;
+ }
+ }
};
await page.route('**/api/chat/delivery-review?*', route => route.fulfill({ json: {
ok: true, goal_id: showcaseGoalId, observed_at: '2026-09-01T10:00:00Z', acceptance: null, graph: null,
@@ -44,6 +53,46 @@ try {
const { page } = session;
try {
await openShowcaseGoal(page);
+ const inlineRail = page.getByTestId('goal-workspace-inline-rail');
+ const inventory = inlineRail.locator('.goal-workspace-inventory');
+ const decision = page.locator('.goal-overview [data-attention-id="showcase-gate-window"]:visible');
+ assert.equal(await decision.count(), 1, 'The owner decision is shown once in the composed Overview');
+ assert.equal(await inlineRail.locator('.goal-all-work').count(), 0, 'The full inventory starts collapsed instead of repeating current work');
+ assert.ok(await decision.locator('.goal-attention-text').evaluate(element => parseFloat(getComputedStyle(element).fontSize) >= 16), 'Owner decisions use readable lead-size text');
+ assert.ok(await decision.locator('svg').first().evaluate(element => {
+ const swatch = document.createElement('span');
+ swatch.style.color = 'var(--pw-caution)';
+ element.parentElement.append(swatch);
+ const matches = getComputedStyle(element).color === getComputedStyle(swatch).color;
+ swatch.remove();
+ return matches;
+ }), 'The owner-decision hand retains the theme gold');
+ await inventory.locator('summary').focus();
+ await page.keyboard.press('Enter');
+ await inlineRail.locator('.goal-work-filters').waitFor({ state: 'visible' });
+ assert.equal(await page.locator('.goal-overview [data-attention-id="showcase-gate-window"]:visible').count(), 1, 'Expanding All work does not duplicate the owner decision');
+ assert.equal(await inlineRail.getByRole('heading', { name: '等待决定', exact: true }).count(), 0, 'Empty work sections do not leave duplicate-looking headings');
+ assert.ok(await inlineRail.locator('.goal-work-row strong').first().evaluate(element => parseFloat(getComputedStyle(element).fontSize) >= 14), 'Inventory tasks use body-size text rather than captions');
+ await inlineRail.getByRole('button', { name: '活动', exact: true }).click();
+ await inlineRail.locator('.goal-material-activity').waitFor({ state: 'visible' });
+ await inlineRail.getByRole('button', { name: '全部工作', exact: true }).click();
+ let historyReads = 0;
+ page.on('request', request => { if (new URL(request.url()).pathname === '/api/chat/completed-todos') historyReads++; });
+ const historyResponse = page.waitForResponse(response => new URL(response.url()).pathname === '/api/chat/completed-todos');
+ await inlineRail.locator('[data-testid="completed-task-lane"] > header button').click();
+ await historyResponse;
+ const overview = page.locator('.goal-overview');
+ await overview.evaluate(element => element.style.maxWidth = '800px');
+ await page.waitForFunction(() => !document.querySelector('.goal-workspace-rail .goal-all-work'));
+ assert.equal(await inventory.isVisible(), false, 'Container reflow hides the inline inventory');
+ const readsBeforeHiddenRefresh = historyReads;
+ state.done++;
+ await page.getByRole('button', { name: '刷新状态', exact: true }).click();
+ await page.waitForFunction(() => document.querySelectorAll('[data-testid="goal-vitals"] .goal-vital')[1]?.querySelector('.goal-vital-value')?.textContent === '24/29');
+ assert.equal(historyReads, readsBeforeHiddenRefresh, 'Refreshing a hidden inventory does not read completed history');
+ await overview.evaluate(element => element.style.removeProperty('max-width'));
+ await inlineRail.locator('.goal-work-filters').waitFor({ state: 'visible' });
+ await inventory.locator('summary').click();
await page.getByRole("region", {name:"当前工作",exact:true}).getByRole("button",{name:"关系图",exact:true}).click();
const heading = page.locator('.personal-channel-title h1');
assert.equal(await heading.innerText(), longTitle, 'The original Goal title remains complete');
@@ -76,10 +125,25 @@ try {
assert.ok(await target.evaluate(element => element === document.activeElement), 'Closing details returns focus to the task');
await page.keyboard.press('Enter'); await page.keyboard.press('Escape');
assert.equal(await map.locator('.work-map-node[aria-pressed="true"]').count(), 0, 'Escape closes the map selection');
+ await inventory.locator('summary').click();
+ await inlineRail.locator('.goal-all-work').waitFor({ state: 'visible' });
await page.setViewportSize({ width: 1024, height: 768 });
+ await page.waitForFunction(() => !document.querySelector('.goal-workspace-rail .goal-all-work'));
+ assert.equal(await inventory.isVisible(), false, 'Narrow view falls back to the All work drawer instead of crowding the brief');
+ assert.equal(await page.locator('.goal-overview [data-attention-id="showcase-gate-window"]:visible').count(), 1, 'The owner decision remains visible once on the narrow page');
+ const openAllWork = page.getByRole('button', { name: '打开全部工作' });
+ await openAllWork.click();
+ const railDialog = page.locator('.goal-work-rail-dialog');
+ assert.ok(await railDialog.locator('.goal-all-work.is-dialog').isVisible(), 'The narrow drawer keeps the same All work inventory reachable');
+ await railDialog.locator('[data-attention-id="showcase-gate-window"]').waitFor({ state: 'visible' });
+ await page.keyboard.press('Escape');
+ assert.equal(await railDialog.isVisible(), false, 'Escape closes the inventory drawer');
+ assert.ok(await openAllWork.evaluate(element => element === document.activeElement), 'Closing the inventory restores focus to the original control');
const measured = await heading.evaluate(element => ({ height: element.clientHeight, line: parseFloat(getComputedStyle(element).lineHeight), overflow: element.scrollWidth - element.clientWidth }));
assert.ok(measured.height > measured.line && measured.overflow <= 1, 'A long title wraps without clipping or page overflow');
await page.setViewportSize({ width: 390, height: 844 });
+ assert.equal(await page.locator('.goal-overview [data-attention-id="showcase-gate-window"]:visible').count(), 1, 'Phone reflow retains one owner decision');
+ assert.ok(await page.locator('.goal-overview-attention .goal-attention-text').first().evaluate(element => parseFloat(getComputedStyle(element).fontSize) >= 16), 'Phone reflow does not shrink the requested reading text');
const headerHeight = await page.locator('.personal-channel-header').evaluate(element => element.getBoundingClientRect().height);
assert.ok(headerHeight <= 220, 'A long Goal title cannot consume the phone viewport');
assert.equal(await heading.evaluate(element => getComputedStyle(element).overflow), 'hidden', 'Phone title clipping must survive the packaged CSS cascade, not just constrain the box height');
@@ -99,7 +163,16 @@ try {
await map.getByRole('button', { name: '关闭事项详情' }).click();
assert.ok(await mobile.evaluate(element => element === document.activeElement));
assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), 'The phone has no page-wide horizontal overflow');
+ state.quiet = true;
+ await page.setViewportSize({ width: 1440, height: 1000 });
+ await page.getByRole('button', { name: '刷新状态', exact: true }).click();
+ await page.waitForFunction(() => document.querySelectorAll('[data-testid="goal-vitals"] .goal-vital')[1]?.querySelector('.goal-vital-value')?.textContent === '0/0');
+ await inlineRail.waitFor({ state: 'detached' });
+ assert.ok(await page.locator('.goal-workspace-composition').evaluate(element => {
+ const frame = element.getBoundingClientRect(), document = element.querySelector('.goal-workspace-document').getBoundingClientRect();
+ return Math.abs(frame.width - document.width) <= 1;
+ }), 'A known-quiet Goal gives its reading column the full width instead of reserving a blank rail');
assert.deepEqual(session.errors, [], 'Packaged composition emits no browser errors');
- console.log('Goal workspace craft: packaged long-title, readable geometry, stationary desktop selection, close/Escape/focus, 390px and reduced-motion checks passed');
+ console.log('Goal workspace craft: single gold attention rail, readable inventory, hidden-history isolation, quiet layout, long-title, stationary selection, close/Escape/focus, 390px and reduced-motion checks passed');
} finally { await session.close(); }
} finally { await browser?.close(); server.kill('SIGTERM'); }
diff --git a/apps/presentation/dashboard/smoke/workspace-command-palette-browser-smoke.mjs b/apps/presentation/dashboard/smoke/workspace-command-palette-browser-smoke.mjs
index 1648e9c9d7..220874cd9a 100644
--- a/apps/presentation/dashboard/smoke/workspace-command-palette-browser-smoke.mjs
+++ b/apps/presentation/dashboard/smoke/workspace-command-palette-browser-smoke.mjs
@@ -76,6 +76,7 @@ try {
await dialogFor(page).getByRole('combobox').fill('map legacy');
await page.keyboard.press('Enter');
await dialogFor(page).waitFor({state: 'hidden'});
+ await page.waitForURL(/todo=showcase-map-columns/);
await page.locator('[data-context-drawer]').waitFor({state: 'visible'});
assert.match(page.url(), /todo=showcase-map-columns/);
// The palette on top of an inspector must not close that inspector on Escape.
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css
index 1ea48763c5..5dd0b5d6e1 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.css
@@ -11,14 +11,13 @@
.goal-overview button:focus-visible { outline: 2px solid var(--pw-blue); outline-offset: 3px; }
.goal-overview button svg { flex: none; }
-.goal-overview-summary { display: grid; gap: 32px; align-items: start; }
-.goal-overview-summary.has-attention { grid-template-columns: minmax(0, 1fr) minmax(260px, 320px); }
-.goal-overview-summary > section { display: grid; align-content: start; gap: 12px; min-width: 0; }
-.goal-overview-summary strong, .goal-overview-summary p, .goal-overview-summary button span { overflow-wrap: anywhere; }
-.goal-overview-summary ul { list-style: none; margin: 0; padding: 0; }
+.goal-overview-direction, .goal-overview-attention { display: grid; align-content: start; gap: 12px; min-width: 0; }
+.goal-overview-direction strong, .goal-overview-direction p, .goal-overview-attention button span { overflow-wrap: anywhere; }
+.goal-overview-attention ul { list-style: none; margin: 0; padding: 0; }
.goal-overview-heading, .goal-overview-attention header { display: flex; align-items: center; gap: 8px; min-height: 32px; }
-.goal-overview-heading h2, .goal-overview-attention h2 { color: var(--pw-muted); font-size: var(--text-caption); font-weight: 500; line-height: var(--leading-caption); }
+.goal-overview-heading h2 { color: var(--pw-muted); font-size: var(--text-caption); font-weight: 500; line-height: var(--leading-caption); }
+.goal-overview-attention h2 { color: var(--pw-text); font-size: var(--text-body); font-weight: 500; line-height: var(--leading-body); }
.goal-overview-heading > span { color: var(--pw-faint); font-size: var(--text-caption); line-height: var(--leading-caption); }
.goal-overview-heading > span::before { content: "·"; padding-right: 8px; }
.goal-overview .goal-overview-heading button { min-height: 32px; padding: 4px 8px; border-color: transparent; background: transparent; color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-caption); }
@@ -62,7 +61,7 @@
.goal-overview-criteria .is-accepted > span { color: var(--pw-muted); }
.goal-overview-criteria .is-failed svg, .goal-overview-criteria .is-failed small { color: var(--pw-red); }
-.goal-overview-summary .goal-overview-run { display: inline-flex; justify-self: start; gap: 8px; margin: 0; padding: 0; min-height: 28px; border: 0; background: transparent; }
+.goal-overview-direction .goal-overview-run { display: inline-flex; justify-self: start; gap: 8px; margin: 0; padding: 0; min-height: 28px; border: 0; background: transparent; }
.goal-overview-run small { color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-caption); }
.goal-overview-run svg { grid-column: 2; grid-row: 1; color: var(--pw-faint); transition: transform var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out); }
.goal-overview-run:hover svg { color: var(--pw-text); transform: translateX(2px); }
@@ -75,11 +74,11 @@
.goal-overview-attention header small { color: var(--pw-faint); }
.goal-overview-attention li { transition: opacity var(--motion-base) var(--ease-out), transform var(--motion-base) var(--ease-out); }
.goal-overview-attention li + li { border-top: 1px solid var(--pw-line); }
-.goal-overview .goal-overview-attention li button { display: grid; grid-template-columns: 16px minmax(0, 1fr) 14px; align-items: start; gap: 10px; width: 100%; min-height: 48px; padding: 10px 8px; border: 0; border-radius: 6px; background: transparent; }
+.goal-overview .goal-overview-attention li button { display: grid; grid-template-columns: 17px minmax(0, 1fr) 14px; align-items: start; gap: 10px; width: 100%; min-height: 48px; padding: 12px 0; border: 0; border-radius: 6px; background: transparent; }
.goal-overview .goal-overview-attention li button:hover { background: var(--pw-hover); }
.goal-overview-attention li svg { margin-top: 2px; }
.goal-overview-attention li svg:first-child { color: var(--pw-caution); }
-.goal-attention-text { display: grid; gap: 2px; min-width: 0; font-size: var(--text-body); line-height: var(--leading-body); }
+.goal-attention-text { display: grid; gap: 4px; min-width: 0; font-size: var(--text-lead); line-height: var(--leading-lead); }
.goal-attention-text time { color: var(--pw-faint); font-size: var(--text-caption); font-variant-numeric: tabular-nums; line-height: var(--leading-caption); }
.goal-overview-attention li svg:last-child { color: var(--pw-faint); transition: transform var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out); }
.goal-overview-attention li button:hover svg:last-child { color: var(--pw-text); transform: translateX(2px); }
@@ -115,9 +114,11 @@
.goal-overview-criteria li, .goal-overview-attention li, .goal-overview-run svg, .goal-overview-attention li svg:last-child { transition: none; }
}
-@media (max-width: 900px) {
- .goal-overview-summary.has-attention { grid-template-columns: 1fr; }
- .goal-overview-attention { padding: 16px 0 0; border-top: 1px solid var(--pw-line); border-left: 0; }
+@media (max-width: 1200px) {
+ .goal-workspace-rail .goal-overview-attention { padding: 16px 0 0; border-top: 1px solid var(--pw-line); border-left: 0; }
+}
+@container (max-width: 920px) {
+ .goal-workspace-rail .goal-overview-attention { padding: 16px 0 0; border-top: 1px solid var(--pw-line); border-left: 0; }
}
@media (max-width: 640px) {
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx
index 562f9975bc..f272038043 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-overview.tsx
@@ -79,8 +79,9 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD
{onMarkSeen ? : null}
) : null}
-
-
+
{digest.objective ? {digest.objective}
: {copy.objectiveUnavailable}
}
{currentWork && currentRow ?
{copy.current} · {goalWorkClassLabel(currentRow.displayClass, locale === "zh-CN")}{currentWork.text}
@@ -92,24 +93,21 @@ export function GoalOverview({ active, goal, items, userTodos, readOnly, onOpenD
onView("tasks")}/>
-
- {showAttention ? }
+ attention={showAttention ?
{copy.attention}
{blocking ?? "—"}
{optional ? {copy.optional.replace("{count}", String(optional))} : null}
- {attention.slice(0, 3).map(item =>
: null}
-
- {copy.remote}
: }/>
{readOnly ?
:
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css
index a88468709c..606d6bdbf4 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.css
@@ -1,10 +1,20 @@
-.goal-workspace-composition { display: grid; grid-template-columns: minmax(0, 1fr); }
+.goal-workspace-composition { display: grid; grid-template-columns: minmax(0, 1fr); gap: clamp(24px, 3vw, 36px); align-items: start; }
+.goal-workspace-composition.has-rail { grid-template-columns: minmax(0, 1fr) minmax(280px, 320px); }
.goal-workspace-document { display: grid; gap: 32px; min-width: 0; }
+.goal-workspace-rail { position: sticky; top: 16px; display: grid; gap: 24px; max-height: calc(100dvh - 32px); min-width: 0; overflow: auto; overscroll-behavior: contain; padding-left: 20px; padding-bottom: 16px; border-left: 1px solid var(--pw-line); }
+.goal-workspace-rail .goal-overview-attention { padding: 0; border: 0; }
+.goal-workspace-inventory { min-width: 0; }
+.goal-workspace-inventory > summary { display: flex; align-items: center; gap: 8px; min-height: 44px; padding: 8px 0; color: var(--pw-text); font-size: var(--text-body); line-height: var(--leading-body); font-weight: 500; list-style: none; cursor: pointer; }
+.goal-workspace-inventory > summary::-webkit-details-marker { display: none; }
+.goal-workspace-inventory > summary > svg:first-child { color: var(--pw-muted); }
+.goal-workspace-inventory > summary > svg:last-child { margin-left: auto; color: var(--pw-faint); }
+.goal-workspace-inventory[open] > summary > svg:last-child { transform: rotate(180deg); }
+.goal-workspace-inventory > summary:focus-visible { outline: 2px solid var(--pw-blue); outline-offset: 3px; border-radius: 6px; }
.goal-now, .goal-workspace-results { min-width: 0; display: grid; gap: 16px; }
.goal-now h2, .goal-workspace-results > h2 { font-size: var(--text-heading); line-height: var(--leading-heading); font-weight: 600; }
.goal-now-header { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }
.goal-work-lenses, .goal-work-filters, .goal-all-work > [role="group"] { display: flex; flex-wrap: wrap; gap: 4px; }
-.goal-work-lenses button, .goal-work-filters button, .goal-all-work > [role="group"] button { border-color: transparent; background: transparent; color: var(--pw-muted); padding: 6px 8px; font-size: var(--text-caption); }
+.goal-work-lenses button, .goal-work-filters button, .goal-all-work > [role="group"] button { border-color: transparent; background: transparent; color: var(--pw-muted); padding: 6px 8px; font-size: var(--text-body); line-height: var(--leading-body); }
.goal-work-lenses button[aria-pressed="true"], .goal-work-filters button[aria-pressed="true"], .goal-all-work > [role="group"] button[aria-pressed="true"] { color: var(--pw-text); background: var(--pw-hover); border-color: var(--pw-line); }
.goal-owner-group { margin-top: 20px; }
.goal-owner-group:first-child { margin-top: 0; }
@@ -31,15 +41,19 @@
.goal-inline-create textarea { width: 100%; min-height: 84px; resize: vertical; border: 1px solid var(--pw-line-strong); border-radius: 6px; color: var(--pw-text); background: var(--pw-bg); padding: 10px; font: inherit; }
.goal-inline-create [role="alert"] { color: var(--pw-red); }
.goal-inline-create form > button { justify-self: start; }
-.goal-all-work { min-width: 0; display: grid; gap: 12px; border-left: 1px solid var(--pw-line); padding-left: 20px; }
+.goal-all-work { min-width: 0; display: grid; gap: 12px; padding-top: 8px; }
.goal-work-search { display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--pw-line); color: var(--pw-muted); }
.goal-work-search input { width: 100%; min-width: 0; border: 0; padding: 10px 0; background: transparent; color: var(--pw-text); font: inherit; font-size: var(--text-body); }
.goal-rail-section { display: grid; gap: 4px; }
-.goal-rail-section h3 { padding-top: 8px; color: var(--pw-muted); font-size: var(--text-caption); font-weight: 500; }
+.goal-rail-section h3 { padding-top: 8px; color: var(--pw-muted); font-size: var(--text-body); line-height: var(--leading-body); font-weight: 500; }
.goal-overview .goal-work-row.is-compact { grid-template-columns: 14px minmax(0, 1fr); padding: 8px 0; gap: 6px; }
-.goal-work-row.is-compact strong { font-size: var(--text-caption); line-height: var(--leading-caption); }
+.goal-work-row.is-compact strong { font-size: var(--text-body); line-height: var(--leading-body); }
.goal-work-row.is-compact .goal-work-badge { grid-column: 2; }
-.goal-rail-section > button:not(.goal-work-row), .goal-material-activity > button { width: 100%; border: 0; border-bottom: 1px solid var(--pw-line); border-radius: 0; background: transparent; padding: 8px 0; font-size: var(--text-caption); }
+.goal-rail-section > button:not(.goal-work-row), .goal-material-activity > button { width: 100%; border: 0; border-bottom: 1px solid var(--pw-line); border-radius: 0; background: transparent; padding: 12px 0; font-size: var(--text-body); line-height: var(--leading-body); }
+.goal-rail-attention > button { display: grid; grid-template-columns: 17px minmax(0, 1fr) 14px; gap: 10px; align-items: start; }
+.goal-rail-attention > button > svg:first-child { color: var(--pw-caution); }
+.goal-rail-attention > button > svg:last-child { color: var(--pw-faint); }
+.goal-rail-attention > button > span { overflow-wrap: anywhere; }
.goal-material-activity { display: grid; gap: 6px; }
.goal-material-activity small { display: block; color: var(--pw-muted); margin-top: 4px; }
.goal-all-work .personal-task-lane-scroll { max-height: 400px; }
@@ -47,8 +61,13 @@
.goal-all-work .personal-completed-row button[aria-pressed="true"] { box-shadow: inset 2px 0 var(--pw-blue); }
.goal-work-ribbon .is-needs_you { background: var(--pw-red); }
@media (max-width: 1200px) {
- .goal-workspace-composition { grid-template-columns: minmax(0, 1fr); }
- .goal-all-work { border-left: 0; border-top: 1px solid var(--pw-line); padding: 20px 0 0; }
+ .goal-workspace-composition.has-rail { grid-template-columns: minmax(0, 1fr); gap: 24px; }
+ .goal-workspace-document, .goal-workspace-rail { display: contents; }
+ .goal-overview-direction { order: 0; }
+ .goal-overview-attention { order: 1; }
+ .goal-now { order: 2; }
+ .goal-workspace-inventory { display: none; }
+ .goal-work-rail-dialog .goal-all-work { padding: 0; }
}
@media (max-width: 600px) {
.goal-overview .goal-work-row { grid-template-columns: 16px minmax(0, 1fr) auto; }
@@ -60,9 +79,13 @@
/* Drawer and sidebar widths change the reading column without a viewport resize. */
.goal-overview { container-type: inline-size; }
@container (max-width: 920px) {
- .goal-workspace-composition { grid-template-columns: minmax(0, 1fr); }
- .goal-all-work { border-left: 0; border-top: 1px solid var(--pw-line); padding: 20px 0 0; }
- .goal-overview-summary.has-attention { grid-template-columns: minmax(0, 1fr); }
+ .goal-workspace-composition.has-rail { grid-template-columns: minmax(0, 1fr); gap: 24px; }
+ .goal-workspace-document, .goal-workspace-rail { display: contents; }
+ .goal-overview-direction { order: 0; }
+ .goal-overview-attention { order: 1; }
+ .goal-now { order: 2; }
+ .goal-workspace-inventory { display: none; }
+ .goal-work-rail-dialog .goal-all-work { padding: 0; }
}
.goal-overview .goal-direction-toggle { display: none; }
.personal-channel-header .personal-channel-actions > .goal-all-work-trigger { display: inline-flex; }
diff --git a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx
index ea1ed59615..83f7e21d84 100644
--- a/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx
+++ b/apps/presentation/dashboard/src/features/personal-workspace/goal-workspace-work.tsx
@@ -1,5 +1,5 @@
import {ContextHelp} from "./context-help";
-import {Circle, CircleAlert, CircleCheck, CircleHelp, Clock, Hand, Info, Play, Plus, Search} from "lucide-react";
+import {ArrowRight, ChevronDown, Circle, CircleAlert, CircleCheck, CircleHelp, Clock, Hand, Info, List, Play, Plus, Search} from "lucide-react";
import {useEffect, useMemo, useRef, useState, type ReactNode} from "react";
import type {GoalWorkDisplayClass, GoalWorkspaceDigest} from "../../../../../../loopx/control_plane/presentation/goal_workspace_digest.js";
import type {WorkspaceActionPreview, WorkspaceActionPreviewRequest, WorkspaceDrawerSelection, WorkspaceGoal, WorkspaceModel, WorkspaceTimelineItem} from "./personal-workspace-model";
@@ -62,8 +62,9 @@ function AddTask({goal, proposals, onPreview, onSelect}: {
;
}
-export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoId, onSelect, board, map,
+export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoId, onSelect, board, map, brief, attention: attentionContent,
readOnly, active, historyRefreshRevision, proposals, onPreview, railOpen, onCloseRail, onOpenAgentConversation}: {
+ brief: ReactNode; attention: ReactNode;
onOpenAgentConversation?: (agentId: string) => void;
railOpen: boolean; onCloseRail: () => void;
goal: WorkspaceGoal; digest: GoalWorkspaceDigest; items: WorkspaceTimelineItem[]; userTodos: WorkspaceModel["userTodos"];
@@ -85,6 +86,9 @@ export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoI
};
const [lens, setLens] = useState<"owner" | "board" | "map">("owner");
const [visited, setVisited] = useState(() => new Set(["owner"]));
+ const [inventoryOpen, setInventoryOpen] = useState(false);
+ const [inventoryAvailable, setInventoryAvailable] = useState(false);
+ const inventory = useRef(null);
const [railTab, setRailTab] = useState<"work" | "activity">("work");
const [filter, setFilter] = useState<"all" | "needsYou" | "open" | "blocked" | "done">("all");
const [query, setQuery] = useState("");
@@ -117,12 +121,24 @@ export function GoalWorkspaceWork({goal, digest, items, userTodos, selectedTodoI
const quietWork = digest.work.coverage === "complete" && digest.work.total === 0
&& digest.needsYou.availability === "ok" && digest.needsYou.blocking === 0
&& digest.needsYou.optional === 0 && !attention.length;
+ const hasRail = Boolean(attentionContent) || !quietWork;
+ useEffect(() => {
+ const node = inventory.current;
+ if (!node) {setInventoryAvailable(false); return;}
+ // Follow the rendered CSS layout, including container-driven reflow, rather
+ // than duplicating its breakpoints in JavaScript. Hidden history must not read.
+ const measure = () => setInventoryAvailable(node.getClientRects().length > 0);
+ const observer = new ResizeObserver(measure);
+ measure();
+ observer.observe(node);
+ return () => observer.disconnect();
+ }, [quietWork]);
const sectionLabels = {needsYou: zh ? "等待决定" : "Needs a decision", open: zh ? "未完成" : "Open", blocked: zh ? "受阻与延后" : "Blocked and deferred", done: zh ? "已完成" : "Done"};
const activity = items.filter(item => (item.kind === "run" && item.run.goalId === goal.goalId && ["completed", "failed", "interrupted"].includes(item.run.status))
|| (item.kind === "output" && item.output.goalId === goal.goalId)
|| (item.kind === "proposal" && item.proposal.goalId === goal.goalId && item.proposal.status === "applied"
&& item.proposal.actionKind === "gate.resolve"));
- const rail =