Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/presentation/dashboard/public/board-preview.css

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions apps/presentation/dashboard/public/board-preview.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>LoopX board preview</title>
<link rel="stylesheet" href="/board-preview.css">
<style>body{margin:0;background:#fbfaf7} .personal-workspace-shell{display:block;min-height:auto} .wrap{width:1384px;margin:0 auto;padding:24px 28px} .personal-task-kanban{grid-template-columns:repeat(4,minmax(0,1fr))} .personal-task-board{padding:0}</style>
</head><body><div class="personal-workspace-shell" data-pw-theme="loopx"><main class="personal-workspace-main"><div class="wrap">
<div class="personal-task-board">
<header class="personal-task-view-toolbar">
<div><strong>Tasks</strong></div>
<label class="personal-task-search"><input type="search" placeholder="Search tasks" aria-label="Search tasks"></label>
<label class="personal-task-filter"><select aria-label="Task state filters"><option>All</option><option>Needs a decision</option><option>Open</option><option>Blocked and deferred</option><option>Done</option></select></label>
<div class="personal-task-view-switch" role="group"><button type="button">List</button><button type="button" aria-pressed="true">Board</button></div>
</header>
<div class="personal-task-kanban">
<section class="personal-object-list personal-task-lane">
<header><strong><i aria-hidden="true" class="personal-kanban-dot tone-attention"></i>Needs your confirmation</strong><span>0</span></header>
<div class="personal-task-lane-scroll"><p class="personal-task-empty">No needs your confirmation.</p></div>
</section><section class="personal-object-list personal-task-lane">
<header><strong><i aria-hidden="true" class="personal-kanban-dot tone-progress"></i>Queued / Running</strong><span>2</span></header>
<div class="personal-task-lane-scroll"><button type="button"><span aria-hidden="true">!</span><strong>确认第 5 批清理清单(3 个零引用辅助函数)</strong><small><span class="personal-row-status is-blocking">待确认</span><span class="personal-task-age">18 天</span></small></button>
<button type="button"><span aria-hidden="true">!</span><strong>是否退役 TR-C 目标:源注册表缺失且状态文件不存在</strong><small><span class="personal-row-status is-blocking">待确认</span><span class="personal-task-age">18 天</span></small></button></div>
</section><section class="personal-object-list personal-task-lane">
<header><strong><i aria-hidden="true" class="personal-kanban-dot tone-schedule"></i>Scheduled & continuous</strong><span>0</span></header>
<div class="personal-task-lane-scroll"><p class="personal-task-empty">No scheduled & continuous.</p></div>
</section><section class="personal-object-list personal-task-lane">
<header><strong><i aria-hidden="true" class="personal-kanban-dot tone-done"></i>Completed</strong><span>2</span></header>
<div class="personal-task-lane-scroll"><button type="button"><span aria-hidden="true">!</span><strong>删除 docker-compose.yml 中仅回滚保留的 cache 卷配置</strong><small><span class="personal-row-status is-pending">进行中</span><span class="personal-task-age">12 分钟</span></small></button>
<button type="button"><span aria-hidden="true">!</span><strong>统一数据版本表述:sstp110 / 本地缓存 / 论文正文</strong><small><span class="personal-row-status is-open">未开始</span><span class="personal-task-age">2 天</span></small></button></div>
</section>
</div>
</div>
</div></main></div></body></html>
4 changes: 4 additions & 0 deletions apps/presentation/dashboard/smoke/home-route-smoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const designSource = readFileSync("design.md", "utf8");

excludes(routerSource, 'view: z.enum(["ops", "share"])', "legacy dual-view routing removed");
excludes(routerSource, 'chatRoute', "legacy standalone chat route removed");
includes(routerSource, 'path: "/design/local-dashboard"', "temporary local dashboard design route");
includes(routerSource, "<DashboardPage search={search} navigate={navigate}/>", "shared dashboard route adapter");
excludes(dashboardSource, "dashboardRoute.useSearch()", "dashboard search is provided by its route adapter");
excludes(dashboardSource, "dashboardRoute.useNavigate()", "dashboard navigation is provided by its route adapter");
includes(dashboardSource, 'data-testid="personal-goal-home"', "personal workspace route");
includes(dashboardSource, "<PersonalWorkspacePage", "personal workspace rendering");
includes(dashboardSource, "buildPersonalHomeModel(payload, rows,", "public-safe workspace projection");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export function ChannelHeader({
{selectedGoal?.loadState ? <p role="status">{t(selectedGoal.loadState === "error" ? "startup.goalError" : "startup.goalLoading")}</p> : null}
</div>
{selectedGoal ? (
<>
<div className="personal-goal-navigation">
<nav aria-label={t("header.goalView")} className="personal-goal-tabs">
<button aria-current={selectedGoalTab === "overview" ? "page" : undefined} onClick={() => onSelectGoalTab("overview")} type="button">{t("header.overview")}</button>
Expand All @@ -262,9 +263,10 @@ export function ChannelHeader({
<button aria-current={selectedGoalTab === "chat" ? "page" : undefined} onClick={() => onSelectGoalTab("chat")} type="button">{t("header.chat")}</button>
<button aria-current={selectedGoalTab === "files" ? "page" : undefined} onClick={() => onSelectGoalTab("files")} type="button">{t("header.files")}</button>
</nav>
{researchControl}
{runtimeControl}
</div>
{researchControl ? <div className="personal-goal-subnav">{researchControl}</div> : null}
</>
) : !scopeUnavailable ? (
<nav aria-label={t("header.managerView")} className="personal-goal-tabs">
<button aria-current={!managerChatOpen ? "page" : undefined} onClick={onReturnManagerHome} type="button">{t("header.managerOverview")}</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {ContextHelp} from "./context-help";
import { ZCodeGoalControl } from "./zcode-goal-control";
import { GoalAcceptanceObservationCard } from "./goal-acceptance-observation-card";
import { goalShortLabel } from "./goal-display";
import { AttentionActions } from "./attention-actions";
import { AttentionDetailCard, AttentionProvenance } from "./attention-detail-card";
import { attentionSuccessor } from "./attention-details";
Expand Down Expand Up @@ -703,7 +704,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<section aria-label={t("drawer.taskInfo")} className="personal-task-inspector-fields">
<h4>{t("drawer.taskInfo")}</h4>
<dl>
<div><dt>Goal</dt><dd>{selection.item.goalTitle}</dd></div>
<div><dt>Goal</dt><dd><span className="drawer-goal-name" title={selection.item.goalTitle}>{selection.item.goalTitle}</span></dd></div>
<div><dt>{t("common.owner")}</dt><dd>{selection.item.claimedBy ? selection.item.ownerLabel ?? selection.item.claimedBy : t("drawer.notAssigned")}</dd></div>
<div><dt>{t("common.status")}</dt><dd>{todoCompleted ? t("drawer.taskStatusCompleted") : selection.item.retryReadback ? t("drawer.resumePending") : selection.item.status === "deferred" ? t("drawer.taskStatusDeferred") : selection.item.status === "blocked" ? t("drawer.taskStatusBlocked") : t("drawer.taskStatusOpen")}</dd></div>
<div><dt>{t("drawer.priority")}</dt><dd>{selection.item.priority ?? t("drawer.notSet")}</dd></div>
Expand Down Expand Up @@ -801,8 +802,8 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<>
<section className="personal-detail-card personal-goal-detail-summary">
<small>{localizedGoalState(selection.item.state, locale)}</small>
<h3>{selection.item.title}</h3>
<p>{selection.item.agentSentence}</p>
<h3 title={selection.item.title}>{goalShortLabel(selection.item)}</h3>
<p className="personal-goal-detail-sentence" title={selection.item.agentSentence}>{selection.item.agentSentence}</p>
</section>
<GoalAcceptanceObservationCard goal={selection.item} />
<section className="personal-detail-card personal-goal-detail-usage">
Expand Down Expand Up @@ -1065,7 +1066,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<h3>{selection.item.title}</h3>
<p>{selection.item.latestActivity}</p>
<dl>
<div><dt>Goal</dt><dd>{selection.item.goalTitle}</dd></div>
<div><dt>Goal</dt><dd><span className="drawer-goal-name" title={selection.item.goalTitle}>{selection.item.goalTitle}</span></dd></div>
<div><dt>{t("drawer.progress")}</dt><dd>{selection.item.completedSteps}/{selection.item.totalSteps}</dd></div>
</dl>
</section>
Expand Down Expand Up @@ -1106,7 +1107,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<h3>{selection.item.title}</h3>
<p>{selection.item.latestActivity}</p>
<dl>
<div><dt>Goal</dt><dd>{selection.item.goalTitle}</dd></div>
<div><dt>Goal</dt><dd><span className="drawer-goal-name" title={selection.item.goalTitle}>{selection.item.goalTitle}</span></dd></div>
<div><dt>{t("drawer.progress")}</dt><dd>{selection.item.completedSteps}/{selection.item.totalSteps}</dd></div>
<div><dt>{t("drawer.sessionStatus")}</dt><dd>{localizedSessionStatus(selection.item.sessionStatus ?? selection.item.status, t)}</dd></div>
<div><dt>{t("drawer.sessionRecoverable")}</dt><dd>{selection.item.resumable === false ? t("drawer.resumeNo") : t("drawer.resumeYes")}</dd></div>
Expand Down Expand Up @@ -1163,7 +1164,7 @@ export function ContextDrawer({ agents, attentionHistory = [], onSelectAttention
<h3>{selection.item.title}</h3>
<p>{selection.item.summary ?? t("drawer.outputRecorded")}</p>
<dl>
<div><dt>Goal</dt><dd>{selection.item.goalTitle ?? selection.item.goalId}</dd></div>
<div><dt>Goal</dt><dd><span className="drawer-goal-name" title={selection.item.goalTitle}>{selection.item.goalTitle ?? selection.item.goalId}</span></dd></div>
<div><dt>{t("drawer.outputTodo")}</dt><dd>{selection.item.todoId ?? t("drawer.notLinked")}</dd></div>
<div><dt>{t("drawer.outputRun")}</dt><dd>{selection.item.runId ?? t("drawer.notLinked")}</dd></div>
<div><dt>Agent</dt><dd>{selection.item.agentLabel ?? selection.item.agentId ?? "LoopX"}</dd></div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export function DeliveryReview(props: DeliveryReviewProps) {
return <DeliveryReviewContent {...props} review={review} />;
}

export function DeliveryReviewContent({ goal, items, userTodos, onSelect, review, active, hideWorkMap = false, hideAcceptanceContract = false }: DeliveryReviewProps & {
review: DeliveryReviewRead; hideWorkMap?: boolean; hideAcceptanceContract?: boolean;
export function DeliveryReviewContent({ goal, items, userTodos, onSelect, review, active, hideWorkMap = false, hideAcceptanceContract = false, hideTitle = false }: DeliveryReviewProps & {
review: DeliveryReviewRead; hideWorkMap?: boolean; hideAcceptanceContract?: boolean; hideTitle?: boolean;
}) {
const { locale } = useWorkspaceI18n();
const copy = deliveryReviewCopy[locale];
Expand Down Expand Up @@ -123,7 +123,7 @@ export function DeliveryReviewContent({ goal, items, userTodos, onSelect, review
}
return <section className="delivery-review" aria-label={copy.title}>
<header className="delivery-review-toolbar">
<h2>{copy.title}</h2>{snapshot ? <SourceNotice review={review} copy={copy} includeBoundary /> : null}
{hideTitle ? null : <h2>{copy.title}</h2>}{snapshot ? <SourceNotice review={review} copy={copy} includeBoundary /> : null}
<div><button type="button" disabled={state.kind === "loading"} onClick={() => review.refresh()}><RefreshCw size={15} />{copy.refresh}</button><button type="button" disabled={!usable} onClick={download}><Download size={15} />{copy.export}</button></div>
</header>
{feedback ? <p role="status">{feedback}</p> : null}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const LABEL_SEPARATORS = /[::,,((… ]/;
const MAX_LABEL = 24;

/**
* One-line label for dense rows. Goal titles are whole objectives, so a row
* takes the clause before the first separator when that clause is short
* enough to read, and otherwise keeps the title for CSS to ellipsize. The full
* title stays in the tooltip and in the Goal's own header.
*/
export function goalShortLabel(goal: { title: string }): string {
const raw = goal.title.trim();
if (raw.length <= MAX_LABEL) return raw;
const head = raw.split(LABEL_SEPARATORS)[0]?.trim() ?? "";
if (head.length >= 4 && head.length <= MAX_LABEL) return head;
return raw;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
.goal-lifecycle-confirm button.is-primary { border-color: var(--pw-text); background: var(--pw-text); color: var(--pw-card); }

/* A stat row, not a table: values carry the weight, labels recede beneath them. */
.goal-vitals { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); column-gap: 8px; border-bottom: 1px solid var(--pw-line); padding: 4px 16px 8px; }
.goal-vitals { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); column-gap: 8px; border-bottom: 1px solid var(--pw-line); padding: 4px 16px 8px; }
.goal-vital-cell { display: grid; grid-template-columns: minmax(0, 1fr) auto; min-width: 0; align-items: start; }
.goal-vital-cell > .context-help { margin-top: 8px; }
.goal-vital-cell > .goal-vital { width: 100%; height: 100%; padding-right: 0; }
Expand Down Expand Up @@ -71,7 +71,7 @@
}

@media (max-width: 900px) {
.goal-vitals { grid-template-columns: repeat(3, minmax(0, 1fr)); padding: 4px 8px 8px; }
.goal-vitals { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); padding: 4px 8px 8px; }
.goal-vital { min-height: 56px; padding: 6px 10px; }
}

Expand Down Expand Up @@ -104,3 +104,23 @@
.goal-vital.is-empty { cursor: default; }
.goal-vital.is-empty:hover { background: transparent; }
.goal-vital.is-empty .goal-vital-value { color: var(--pw-faint); font-size: var(--text-body); font-weight: 500; letter-spacing: 0; }

/* Stage bar: the Goal's position in Plan → Run → Accept, in one line. */
.goal-stagebar { display: flex; align-items: center; gap: 24px; padding: 8px 16px 10px; border-bottom: 1px solid var(--pw-line); flex-wrap: wrap; }
.goal-stagebar-track { display: flex; align-items: center; margin: 0; padding: 0; list-style: none; }
.goal-stagebar-track li { display: flex; align-items: center; }
.goal-stagebar-track li + li::before { content: ""; width: 24px; height: 1px; margin: 0 10px; background: var(--pw-line-strong); }
.goal-stagebar-track button { display: inline-flex; align-items: center; gap: 7px; padding: 4px 0; border: 0; background: none; color: var(--pw-muted); font: inherit; font-size: var(--text-caption); line-height: var(--leading-caption); cursor: pointer; }
.goal-stagebar-track button:focus-visible { outline: 2px solid var(--pw-blue); outline-offset: 3px; border-radius: 4px; }
.goal-stagebar-dot { width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid var(--pw-line-strong); background: transparent; box-sizing: border-box; }
.goal-stagebar-track .is-done .goal-stagebar-dot { border-color: var(--pw-text); background: var(--pw-text); }
.goal-stagebar-track .is-done button { color: var(--pw-text); }
.goal-stagebar-track .is-current .goal-stagebar-dot { border-color: var(--pw-blue); background: var(--pw-blue); box-shadow: 0 0 0 3px rgb(47 102 233 / 14%); }
.goal-stagebar-track .is-current button { color: var(--pw-text); font-weight: 600; }
.goal-stagebar-meta { display: flex; align-items: center; gap: 14px; margin-left: auto; color: var(--pw-muted); font-size: var(--text-caption); line-height: var(--leading-caption); font-variant-numeric: tabular-nums; }
.goal-stagebar-state { display: inline-block; width: 7px; height: 7px; margin-right: 6px; border-radius: 50%; background: var(--pw-faint); vertical-align: middle; }
.goal-stagebar-state.is-running { background: var(--pw-blue); }
.goal-stagebar-state.is-attention { background: var(--pw-amber); }
.goal-stagebar-state.is-danger { background: var(--pw-red); }
.goal-stagebar-state.is-queued, .goal-stagebar-state.is-stopped { background: #c7c7c7; }
.goal-stagebar-wait { color: var(--pw-text); }
Loading
Loading