From f3988de4f5be1aa6ae79544360263338483b6cae Mon Sep 17 00:00:00 2001 From: HugoXK Date: Sun, 15 Nov 2026 16:08:46 +0800 Subject: [PATCH] ui: split the repository header into a status strip and facts Fixes #811 --- changelog.d/811-repo-header.changed.md | 1 + docs/architecture.md | 6 +++++- public/app.css | 11 +++++++++++ src/views/pages.ts | 15 ++++++++++++--- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 changelog.d/811-repo-header.changed.md diff --git a/changelog.d/811-repo-header.changed.md b/changelog.d/811-repo-header.changed.md new file mode 100644 index 00000000..e6bd2708 --- /dev/null +++ b/changelog.d/811-repo-header.changed.md @@ -0,0 +1 @@ +The repository header now separates the title and target branch, a compact status strip (automatic work, discovery interval, next run, schedule console, FlyBot and GitHub credential) and a facts line, instead of one long paragraph carrying every fact. diff --git a/docs/architecture.md b/docs/architecture.md index 505c6699..e73ab39a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -295,7 +295,11 @@ recorded". `scripts/restore-drill.mjs --status-file` is the writer. opens the section that holds it — so a reader without the script still gets the whole page. The bar sticks at `var(--header-h)` and scrolls horizontally in the phone band; the tab is a 44px `--touch` target and the current tab - paints with `--accent-strong`, the one interactive accent. + paints with `--accent-strong`, the one interactive accent. Above the tabs, the + header separates the title and target branch, a status strip (automatic work, + discovery interval, next run, schedule console, FlyBot, GitHub credential) and + a facts line (upstream/fork, permission, role, reviewer); the run/plan/review + actions stay in the header and every existing anchor is unchanged. - **Dashboard job filters** (`src/lib/jobFilters.ts`): `/dashboard` accepts `repo`, `status` and `kind` as GET parameters and applies them in SQL, so the count, the page and the pagination links describe the same list; a value the diff --git a/public/app.css b/public/app.css index 9338c641..78816b70 100644 --- a/public/app.css +++ b/public/app.css @@ -514,6 +514,17 @@ p { margin: 0 0 12px; } inner-id offset, so the two cannot drift apart. */ .repo-page { --subnav-h: calc(var(--touch) + 2 * var(--space-2) + 1px); } .repo-page [id] { scroll-margin-top: calc(var(--header-h) + var(--subnav-h) + var(--space-4)); } +.repo-head { align-items: flex-start; } +.repo-head-main { display: grid; gap: 8px; min-width: 0; } +.repo-status-strip { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; min-width: 0; } +.repo-status { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; padding: 4px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--field); color: var(--text); font-size: var(--text-sm); text-decoration: none; } +.repo-status .fine { color: var(--muted); } +.repo-status b { font-weight: 650; } +a.repo-status:hover { border-color: var(--accent); } +@media (max-width: 680px) { + .repo-status-strip { gap: 6px; } + .repo-status { flex: 1 1 calc(50% - 6px); } +} .repo-subnav { position: sticky; top: var(--header-h); z-index: 15; display: flex; gap: var(--space-2); margin: 0 0 var(--space-4); min-height: var(--subnav-h); diff --git a/src/views/pages.ts b/src/views/pages.ts index 2020f48c..21567941 100644 --- a/src/views/pages.ts +++ b/src/views/pages.ts @@ -1341,11 +1341,20 @@ export function repoPage(input: { config: Config; locale: Locale; viewer: Viewer const teamGrid = teamRoles.length === 0 ? '' : `
${teamRoles.map((role) => `
${escapeHtml(role.label)}${escapeHtml(role.note)}
`).join('')}
${escapeHtml(t('查看 session ID', 'View session IDs'))}
${teamRoles.map((role) => `${escapeHtml(role.label)}: ${escapeHtml(role.id)}`).join('')}
` const repoHeader = `
-
-
+
+
${escapeHtml(t('仓库', 'REPOSITORY'))}

${escapeHtml(upstreamOwner)}/${escapeHtml(upstreamName)}

-

${escapeHtml(t('目标分支', 'Target branch'))} ${escapeHtml(targetBranch)}${targetBranch === defaultBranch ? '' : ` · ${escapeHtml(t('默认分支', 'Default branch'))} ${escapeHtml(defaultBranch)}`} · ${escapeHtml(repo.auto_iterate === 1 ? t('自动迭代已开启', 'Auto-iteration on') : t('仅手动运行', 'Manual runs only'))} · ${escapeHtml(t('自动间隔', 'Auto every'))} ${escapeHtml(intervalLabel(repo.interval_minutes, t))} · ${escapeHtml(t('下次约', 'Next'))} ${escapeHtml(nextRunText)} · ${escapeHtml(t('排期控制台', 'Schedule console'))} · ${badge(t, repo.last_status ?? 'idle')}

+

${escapeHtml(t('目标分支', 'Target branch'))} ${escapeHtml(targetBranch)}${targetBranch === defaultBranch ? '' : ` · ${escapeHtml(t('默认分支', 'Default branch'))} ${escapeHtml(defaultBranch)}`}

+
+ ${escapeHtml(t('自动迭代', 'Automatic work'))}${escapeHtml(repo.auto_iterate === 1 ? t('自动迭代已开启', 'Auto-iteration on') : t('仅手动运行', 'Manual runs only'))} + ${escapeHtml(t('自动间隔', 'Auto every'))}${escapeHtml(intervalLabel(repo.interval_minutes, t))} + ${escapeHtml(t('下次约', 'Next'))}${escapeHtml(nextRunText)} + ${escapeHtml(t('排期控制台', 'Schedule console'))} + FlyBot${flybotAvailable ? (instance === undefined ? escapeHtml(t('未创建', 'not created')) : badge(t, instance.status)) : escapeHtml(t('未启用', 'not enabled'))} + ${escapeHtml(t('GitHub 凭据', 'GitHub credential'))}${escapeHtml(credentialOk ? t('正常', 'ok') : t('失效', 'invalid'))} + ${badge(t, repo.last_status ?? 'idle')} +

${escapeHtml(t('上游', 'Upstream'))} ${escapeHtml(upstreamOwner)}/${escapeHtml(upstreamName)} · ${repo.is_fork === 1 ? `${escapeHtml(t('Fork', 'Fork'))} ${escapeHtml(forkOwner)}/${escapeHtml(forkName)}` : escapeHtml(t('自有仓库,无需 fork', 'Own repository, no fork'))} · ${escapeHtml(t('GitHub 权限', 'GitHub permission'))} ${escapeHtml(permission)} · ${escapeHtml(t('你的角色', 'Your role'))} ${escapeHtml(repoRoleLabel(t, repoRole))} · ${escapeHtml(reviewerLabel)}