chore: sync storyforge handoff state
This commit is contained in:
692
docs/superpowers/plans/2026-03-28-homepage-workbench-redesign.md
Normal file
692
docs/superpowers/plans/2026-03-28-homepage-workbench-redesign.md
Normal file
@@ -0,0 +1,692 @@
|
||||
# Homepage Workbench Redesign Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Rebuild the StoryForge homepage into the approved human-first `v6` structure while preserving the current visual language, reducing text density, surfacing `1 主 2 次` actions first, and moving system governance entry points into an explicit admin workbench flow.
|
||||
|
||||
**Architecture:** Keep the existing static-script frontend architecture, but pull homepage-specific rendering into a dedicated browser module so the dashboard layout can be tested without dragging the entire `app.js` file into every change. The existing `renderDashboardScreen()` function becomes an orchestrator: it gathers runtime data, delegates HTML generation to a dedicated homepage renderer, and wires click handlers through the existing global action system and quick-action modal.
|
||||
|
||||
**Tech Stack:** Vanilla browser JS (IIFE modules on `window`), HTML string rendering, CSS in `assets/styles.css`, Python baseline tests, Node built-in test runner for homepage markup contracts.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Extract Homepage Rendering Into a Dedicated Module
|
||||
|
||||
**Files:**
|
||||
- Create: `web/storyforge-web-v4/assets/storyforge-dashboard-home.js`
|
||||
- Create: `web/storyforge-web-v4/tests/dashboard-home.test.mjs`
|
||||
- Modify: `web/storyforge-web-v4/index.html`
|
||||
- Modify: `web/storyforge-web-v4/assets/app.js`
|
||||
|
||||
- [ ] **Step 1: Write the failing homepage renderer test**
|
||||
|
||||
Create `web/storyforge-web-v4/tests/dashboard-home.test.mjs`:
|
||||
|
||||
```js
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import vm from "node:vm";
|
||||
|
||||
const ROOT = path.resolve(process.cwd(), "web/storyforge-web-v4");
|
||||
|
||||
function loadHomepageModule() {
|
||||
const source = fs.readFileSync(path.join(ROOT, "assets/storyforge-dashboard-home.js"), "utf8");
|
||||
const context = {
|
||||
window: {},
|
||||
console,
|
||||
escapeHtml: (value) => String(value ?? ""),
|
||||
formatNumber: (value) => String(value ?? 0),
|
||||
safeArray: (value) => Array.isArray(value) ? value : [],
|
||||
button: (label, action, tone = "secondary") =>
|
||||
`<button class="btn btn-${tone}" data-action="${action}">${label}</button>`
|
||||
};
|
||||
vm.createContext(context);
|
||||
vm.runInContext(source, context);
|
||||
return context.window.StoryForgeDashboardHome;
|
||||
}
|
||||
|
||||
test("homepage v6 puts actions before overview and uses 1-primary-2-secondary structure", () => {
|
||||
const mod = loadHomepageModule();
|
||||
const html = mod.renderDashboardHome({
|
||||
title: "项目总台",
|
||||
workspaceLabel: "Kris",
|
||||
currentProjectName: "品牌增长实验室",
|
||||
summaryTabs: [
|
||||
{ key: "project_progress", label: "项目进度", value: "3 / 5", hint: "2 项可继续推进", active: true },
|
||||
{ key: "focus_accounts", label: "重点账号 / 对标", value: "2 个", hint: "1 个缺高分分析", active: false },
|
||||
{ key: "production_jobs", label: "生产任务", value: "4 条", hint: "1 条待确认", active: false }
|
||||
],
|
||||
primaryAction: {
|
||||
title: "先补抖音重点对标的高分作品分析",
|
||||
reason: "最近有新作品,但还没形成高分样本。",
|
||||
badges: ["最优先", "预计 10 分钟判断", "关联:重点账号"]
|
||||
},
|
||||
secondaryActions: [
|
||||
{ title: "确认一个待执行的生产计划", reason: "素材和结论都在,只差最后确认。" },
|
||||
{ title: "更新重点账号的跟踪摘要", reason: "有新动态,但不值得占据大块首页空间。" }
|
||||
],
|
||||
overviewDetail: {
|
||||
title: "当前阶段",
|
||||
body: "这里只展示当前 tab 的核心状态。"
|
||||
}
|
||||
});
|
||||
|
||||
assert.ok(html.includes("今天先做什么"));
|
||||
assert.ok(html.includes("项目概览"));
|
||||
assert.ok(html.indexOf("今天先做什么") < html.indexOf("项目概览"));
|
||||
assert.match(html, /先补抖音重点对标的高分作品分析/);
|
||||
assert.match(html, /确认一个待执行的生产计划/);
|
||||
assert.match(html, /更新重点账号的跟踪摘要/);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the new test and verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
```
|
||||
|
||||
Expected: FAIL with `ENOENT` for `storyforge-dashboard-home.js`.
|
||||
|
||||
- [ ] **Step 3: Create the dedicated homepage renderer module**
|
||||
|
||||
Create `web/storyforge-web-v4/assets/storyforge-dashboard-home.js`:
|
||||
|
||||
```js
|
||||
(function () {
|
||||
function defaultEscapeHtml(value) {
|
||||
return String(value ?? "")
|
||||
.replaceAll("&", "&")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll('"', """);
|
||||
}
|
||||
|
||||
function renderTags(items, escapeHtml) {
|
||||
return (items || []).map((item) => `<span class="tag">${escapeHtml(item)}</span>`).join("");
|
||||
}
|
||||
|
||||
function renderSecondaryAction(item, index, escapeHtml) {
|
||||
return `
|
||||
<div class="dashboard-action-secondary">
|
||||
<div class="dashboard-action-index">${index + 2}</div>
|
||||
<div>
|
||||
<h5>${escapeHtml(item.title)}</h5>
|
||||
<p>${escapeHtml(item.reason)}</p>
|
||||
</div>
|
||||
<div class="dashboard-action-buttons">
|
||||
<button class="btn btn-ghost" data-action="${escapeHtml(item.reasonAction || "open-action-reason")}">原因</button>
|
||||
<button class="btn btn-secondary" data-action="${escapeHtml(item.goAction || "goto-production")}">${escapeHtml(item.goLabel || "去处理")}</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderDashboardHome(model, helpers = {}) {
|
||||
const escapeHtml = helpers.escapeHtml || defaultEscapeHtml;
|
||||
return `
|
||||
<div class="dashboard-home">
|
||||
<div class="dashboard-context-row">
|
||||
<div class="dashboard-context-left">
|
||||
<div class="dashboard-context-chip">
|
||||
<strong>当前工作区</strong><span>${escapeHtml(model.workspaceLabel)}</span>
|
||||
</div>
|
||||
<button class="dashboard-context-chip" data-action="open-dashboard-project-switcher">
|
||||
<strong>当前项目</strong><span>${escapeHtml(model.currentProjectName)}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="dashboard-context-right">
|
||||
${model.contextLinks.map((item) => `
|
||||
<button class="dashboard-context-chip" data-action="${escapeHtml(item.action)}">
|
||||
<span>${escapeHtml(item.label)}</span><strong>${escapeHtml(item.value)}</strong>
|
||||
</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel pad dashboard-priority-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h3>今天先做什么</h3>
|
||||
<div class="panel-subtitle">先做决定,再看细节。</div>
|
||||
</div>
|
||||
<span class="tag blue">${escapeHtml(model.actionSourceLabel)}</span>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-action-primary">
|
||||
<div>
|
||||
<h4>${escapeHtml(model.primaryAction.title)}</h4>
|
||||
<p>${escapeHtml(model.primaryAction.reason)}</p>
|
||||
<div class="task-meta">${renderTags(model.primaryAction.badges, escapeHtml)}</div>
|
||||
</div>
|
||||
<div class="dashboard-action-buttons">
|
||||
<button class="btn btn-ghost" data-action="open-action-reason">查看原因</button>
|
||||
<button class="btn btn-secondary" data-action="${escapeHtml(model.primaryAction.goAction)}">${escapeHtml(model.primaryAction.goLabel)}</button>
|
||||
<button class="btn btn-primary" data-action="open-oneliner">${escapeHtml(model.primaryAction.agentLabel)}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-action-secondary-list">
|
||||
${model.secondaryActions.map((item, index) => renderSecondaryAction(item, index, escapeHtml)).join("")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel pad dashboard-overview-panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h3>项目概览</h3>
|
||||
<div class="panel-subtitle">按需展开,不抢首页第一优先级。</div>
|
||||
</div>
|
||||
<span class="tag">${escapeHtml(model.activeTabLabel)}</span>
|
||||
</div>
|
||||
<div class="dashboard-overview-tabs">
|
||||
${model.summaryTabs.map((item) => `
|
||||
<button class="dashboard-overview-tab ${item.active ? "is-active" : ""}" data-action="select-dashboard-tab" data-dashboard-tab="${escapeHtml(item.key)}">
|
||||
<small>${escapeHtml(item.label)}</small>
|
||||
<strong>${escapeHtml(item.value)}</strong>
|
||||
<span>${escapeHtml(item.hint)}</span>
|
||||
</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
<div class="dashboard-overview-body">${model.overviewBodyHtml}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
window.StoryForgeDashboardHome = {
|
||||
renderDashboardHome
|
||||
};
|
||||
})();
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Wire the new module into the page**
|
||||
|
||||
Modify `web/storyforge-web-v4/index.html`:
|
||||
|
||||
```html
|
||||
<script src="./assets/storyforge-dashboard-home.js"></script>
|
||||
<script src="./assets/app.js"></script>
|
||||
```
|
||||
|
||||
Modify `web/storyforge-web-v4/assets/app.js` near `renderDashboardScreen()`:
|
||||
|
||||
```js
|
||||
const dashboardHomeRenderer = window.StoryForgeDashboardHome;
|
||||
|
||||
function renderDashboardScreen() {
|
||||
// existing auth/loading guards stay in place
|
||||
const homeModel = buildDashboardHomeModel();
|
||||
return screenShell(
|
||||
"项目总台",
|
||||
"先做最能推进当前项目的事。",
|
||||
`${button("新建项目", "create-project")} ${button("导入主页", "open-import-homepage")} ${button("创建 Agent", "open-create-assistant", "primary")}`,
|
||||
dashboardHomeRenderer.renderDashboardHome(homeModel, { escapeHtml })
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Re-run the renderer test and syntax checks**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
node --check web/storyforge-web-v4/assets/storyforge-dashboard-home.js
|
||||
node --check web/storyforge-web-v4/assets/app.js
|
||||
```
|
||||
|
||||
Expected: all PASS with the Node test showing `ok 1`.
|
||||
|
||||
- [ ] **Step 6: Commit the extraction**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
git add web/storyforge-web-v4/assets/storyforge-dashboard-home.js web/storyforge-web-v4/tests/dashboard-home.test.mjs web/storyforge-web-v4/index.html web/storyforge-web-v4/assets/app.js
|
||||
git commit -m "feat: extract homepage dashboard renderer"
|
||||
```
|
||||
|
||||
### Task 2: Implement Human-First Dashboard Data Model and 1-Primary-2-Secondary Actions
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/storyforge-web-v4/assets/storyforge-dashboard-home.js`
|
||||
- Modify: `web/storyforge-web-v4/assets/app.js`
|
||||
- Modify: `web/storyforge-web-v4/tests/dashboard-home.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Add failing tests for homepage model generation**
|
||||
|
||||
Append to `web/storyforge-web-v4/tests/dashboard-home.test.mjs`:
|
||||
|
||||
```js
|
||||
test("homepage model builds one primary action, two secondary actions, and a rule fallback label", () => {
|
||||
const mod = loadHomepageModule();
|
||||
assert.equal(typeof mod.createDashboardHomeModel, "function");
|
||||
|
||||
const model = mod.createDashboardHomeModel({
|
||||
workspaceLabel: "Kris",
|
||||
currentProjectName: "品牌增长实验室",
|
||||
trackedAccountsCount: 2,
|
||||
assistantCount: 1,
|
||||
jobCount: 4,
|
||||
actionSourceLabel: "规则推荐",
|
||||
dashboardOverviewTab: "project_progress"
|
||||
});
|
||||
|
||||
assert.equal(model.actionSourceLabel, "规则推荐");
|
||||
assert.equal(model.secondaryActions.length, 2);
|
||||
assert.match(model.primaryAction.title, /高分作品分析|继续补高分对标/);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the targeted Node tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
```
|
||||
|
||||
Expected: FAIL because the renderer does not yet expose the full `contextLinks` / `actionSourceLabel` model consistently.
|
||||
|
||||
- [ ] **Step 3: Add a reusable homepage model builder in `storyforge-dashboard-home.js`**
|
||||
|
||||
Modify `web/storyforge-web-v4/assets/storyforge-dashboard-home.js`:
|
||||
|
||||
```js
|
||||
function createDashboardHomeModel(raw) {
|
||||
const trackedAccountsCount = Number(raw.trackedAccountsCount || 0);
|
||||
const assistantCount = Number(raw.assistantCount || 0);
|
||||
const jobCount = Number(raw.jobCount || 0);
|
||||
|
||||
const actions = [];
|
||||
if (trackedAccountsCount > 0) {
|
||||
actions.push({
|
||||
title: "先补抖音重点对标的高分作品分析",
|
||||
reason: "最近有新作品,但还没形成高分样本。",
|
||||
badges: ["最优先", "预计 10 分钟判断", "关联:重点账号"],
|
||||
goAction: "goto-discovery",
|
||||
goLabel: "去找对标",
|
||||
agentLabel: "交给主 Agent"
|
||||
});
|
||||
}
|
||||
if (jobCount > 0) {
|
||||
actions.push({
|
||||
title: "确认一个待执行的生产计划",
|
||||
reason: "素材和结论都在,只差最后确认。",
|
||||
goAction: "goto-production",
|
||||
goLabel: "去处理"
|
||||
});
|
||||
}
|
||||
actions.push({
|
||||
title: "更新重点账号的跟踪摘要",
|
||||
reason: "有新动态,但不值得占据大块首页空间。",
|
||||
goAction: "goto-tracking",
|
||||
goLabel: "去处理"
|
||||
});
|
||||
while (actions.length < 3) {
|
||||
actions.push({
|
||||
title: "继续补高分对标并安排生产",
|
||||
reason: "当前项目没有更多高优先动作时,保持主流程推进。",
|
||||
goAction: "goto-production",
|
||||
goLabel: "去处理"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
workspaceLabel: raw.workspaceLabel,
|
||||
currentProjectName: raw.currentProjectName,
|
||||
actionSourceLabel: raw.actionSourceLabel,
|
||||
contextLinks: [
|
||||
{ label: "账号", value: String(trackedAccountsCount), action: "goto-owned" },
|
||||
{ label: "任务", value: String(jobCount), action: "goto-production" },
|
||||
{ label: "Agent", value: String(assistantCount), action: "goto-playbook" }
|
||||
],
|
||||
primaryAction: actions[0],
|
||||
secondaryActions: actions.slice(1, 3)
|
||||
};
|
||||
}
|
||||
|
||||
window.StoryForgeDashboardHome = {
|
||||
createDashboardHomeModel,
|
||||
renderDashboardHome
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add dashboard-specific state and wire the model builder from `app.js`**
|
||||
|
||||
Modify `web/storyforge-web-v4/assets/app.js` state setup:
|
||||
|
||||
```js
|
||||
const appState = {
|
||||
// existing fields...
|
||||
dashboardOverviewTab: "project_progress",
|
||||
dashboardActionReason: null
|
||||
};
|
||||
```
|
||||
|
||||
Build the raw dashboard inputs in `web/storyforge-web-v4/assets/app.js`:
|
||||
|
||||
```js
|
||||
function getDashboardActionSourceLabel() {
|
||||
return appState.onelinerProfile ? "主 Agent 优先推荐" : "规则推荐";
|
||||
}
|
||||
|
||||
function buildDashboardHomeModel() {
|
||||
const project = getSelectedProject();
|
||||
const stats = project ? getProjectStats(project.id) : { assistants: [], jobs: [], sources: [], knowledgeBases: [] };
|
||||
const trackedAccounts = getTrackingAccounts();
|
||||
const baseModel = window.StoryForgeDashboardHome.createDashboardHomeModel({
|
||||
workspaceLabel: appState.me?.display_name || appState.me?.username || "当前工作区",
|
||||
currentProjectName: project?.name || "还没有项目",
|
||||
trackedAccountsCount: trackedAccounts.length || appState.accounts.length,
|
||||
assistantCount: stats.assistants.length,
|
||||
jobCount: stats.jobs.length,
|
||||
actionSourceLabel: getDashboardActionSourceLabel(),
|
||||
dashboardOverviewTab: appState.dashboardOverviewTab
|
||||
});
|
||||
return {
|
||||
...baseModel,
|
||||
summaryTabs: buildDashboardOverviewTabs(project, stats),
|
||||
activeTabLabel: dashboardTabLabel(appState.dashboardOverviewTab),
|
||||
overviewBodyHtml: renderDashboardOverviewBody(appState.dashboardOverviewTab, { project, stats, trackedAccounts })
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Re-run tests and syntax checks**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
node --check web/storyforge-web-v4/assets/storyforge-dashboard-home.js
|
||||
node --check web/storyforge-web-v4/assets/app.js
|
||||
```
|
||||
|
||||
Expected: PASS with no missing-field errors.
|
||||
|
||||
- [ ] **Step 6: Commit the action hierarchy work**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
git add web/storyforge-web-v4/assets/storyforge-dashboard-home.js web/storyforge-web-v4/assets/app.js web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
git commit -m "feat: redesign dashboard actions for human-first flow"
|
||||
```
|
||||
|
||||
### Task 3: Implement Overview Tabs, Project Switcher, and Admin Workbench Entry
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/storyforge-web-v4/index.html`
|
||||
- Modify: `web/storyforge-web-v4/assets/app.js`
|
||||
- Modify: `web/storyforge-web-v4/assets/storyforge-dashboard-home.js`
|
||||
- Modify: `web/storyforge-web-v4/tests/dashboard-home.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Add failing tests for overview tab buttons and admin entry**
|
||||
|
||||
Append to `web/storyforge-web-v4/tests/dashboard-home.test.mjs`:
|
||||
|
||||
```js
|
||||
test("homepage overview uses tab buttons and does not render legacy repeated sections", () => {
|
||||
const mod = loadHomepageModule();
|
||||
const html = mod.renderDashboardHome({
|
||||
workspaceLabel: "Kris",
|
||||
currentProjectName: "品牌增长实验室",
|
||||
contextLinks: [],
|
||||
actionSourceLabel: "主 Agent 优先推荐",
|
||||
primaryAction: { title: "A", reason: "B", badges: [], goAction: "x", goLabel: "去处理", agentLabel: "交给主 Agent" },
|
||||
secondaryActions: [],
|
||||
summaryTabs: [
|
||||
{ key: "project_progress", label: "项目进度", value: "3 / 5", hint: "2 项可继续推进", active: true }
|
||||
],
|
||||
activeTabLabel: "项目进度",
|
||||
overviewBodyHtml: "<section>tab body</section>"
|
||||
});
|
||||
|
||||
assert.ok(html.includes('data-action="select-dashboard-tab"'));
|
||||
assert.ok(!html.includes("当前项目推进详情"));
|
||||
assert.ok(!html.includes("重点账号 / 对标</h3><div class=\"panel-subtitle\">右栏保留"));
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the Node test and verify the new assertions fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
```
|
||||
|
||||
Expected: FAIL because the overview renderer and admin entry are not complete yet.
|
||||
|
||||
- [ ] **Step 3: Implement overview-tab state and project switcher reuse**
|
||||
|
||||
Modify `web/storyforge-web-v4/assets/app.js`:
|
||||
|
||||
```js
|
||||
function dashboardTabLabel(value) {
|
||||
return ({
|
||||
project_progress: "项目进度",
|
||||
focus_accounts: "重点账号 / 对标",
|
||||
production_jobs: "生产任务"
|
||||
})[value] || "项目进度";
|
||||
}
|
||||
|
||||
function buildDashboardOverviewTabs(project, stats) {
|
||||
return [
|
||||
{ key: "project_progress", label: "项目进度", value: "3 / 5", hint: "2 项可继续推进", active: appState.dashboardOverviewTab === "project_progress" },
|
||||
{ key: "focus_accounts", label: "重点账号 / 对标", value: formatNumber(getTrackingAccounts().length), hint: "重点对象", active: appState.dashboardOverviewTab === "focus_accounts" },
|
||||
{ key: "production_jobs", label: "生产任务", value: formatNumber(stats.jobs.length), hint: "当前项目任务", active: appState.dashboardOverviewTab === "production_jobs" }
|
||||
];
|
||||
}
|
||||
|
||||
function openDashboardProjectSwitcher() {
|
||||
openActionModal({
|
||||
title: "切换当前项目",
|
||||
description: "首页上下文与动作区会随当前项目一起切换。",
|
||||
submitLabel: "切换项目",
|
||||
fields: [
|
||||
{ name: "projectId", label: "当前项目", type: "select", value: getSelectedProject()?.id || "", options: getProjectOptions() }
|
||||
],
|
||||
onSubmit: async (payload) => {
|
||||
appState.selectedProjectId = payload.projectId;
|
||||
await loadAgentControlSurfaces(appState.selectedProjectId || "");
|
||||
renderAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
Add click handling in `web/storyforge-web-v4/assets/app.js`:
|
||||
|
||||
```js
|
||||
if (name === "select-dashboard-tab") {
|
||||
appState.dashboardOverviewTab = action.dataset.dashboardTab || "project_progress";
|
||||
renderAll();
|
||||
return;
|
||||
}
|
||||
if (name === "open-dashboard-project-switcher") {
|
||||
openDashboardProjectSwitcher();
|
||||
return;
|
||||
}
|
||||
if (name === "goto-owned") {
|
||||
setScreen("owned");
|
||||
return;
|
||||
}
|
||||
if (name === "goto-tracking") {
|
||||
setScreen("tracking");
|
||||
return;
|
||||
}
|
||||
if (name === "goto-playbook") {
|
||||
setScreen("playbook");
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add the explicit admin workbench entry and screen**
|
||||
|
||||
Modify `web/storyforge-web-v4/index.html` sidebar:
|
||||
|
||||
```html
|
||||
<button class="nav-item hidden" data-screen-target="admin-workbench" data-role-gate="super_admin">
|
||||
<span class="icon">⚙</span>
|
||||
<span>管理员配置台</span>
|
||||
</button>
|
||||
```
|
||||
|
||||
Modify `web/storyforge-web-v4/assets/app.js`:
|
||||
|
||||
```js
|
||||
function syncRoleGatedNav() {
|
||||
document.querySelectorAll("[data-role-gate]").forEach((element) => {
|
||||
const gate = element.getAttribute("data-role-gate");
|
||||
const visible = gate === "super_admin" ? isSuperAdmin() : true;
|
||||
element.classList.toggle("hidden", !visible);
|
||||
});
|
||||
}
|
||||
|
||||
function renderAdminWorkbenchScreen() {
|
||||
if (!isSuperAdmin()) {
|
||||
return screenShell("管理员配置台", "仅超级管理员可见。", "", renderEmptyState("无权限", "请使用超级管理员账号访问。"));
|
||||
}
|
||||
return screenShell(
|
||||
"管理员配置台",
|
||||
"系统级依赖、存储、平台 Agent 与策略治理。",
|
||||
"",
|
||||
`
|
||||
${renderIntegrationOverviewPanel()}
|
||||
${renderStorageStatusPanel()}
|
||||
${renderPlatformAgentPanel()}
|
||||
${renderAdminOpsOverviewPanel()}
|
||||
${renderAdminFixRunsPanel()}
|
||||
`
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Call `syncRoleGatedNav()` inside `renderAll()` after session/role state has updated.
|
||||
|
||||
- [ ] **Step 5: Re-run targeted tests and syntax checks**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
node --check web/storyforge-web-v4/assets/storyforge-dashboard-home.js
|
||||
node --check web/storyforge-web-v4/assets/app.js
|
||||
```
|
||||
|
||||
Expected: PASS, and homepage markup no longer contains legacy repeated panels.
|
||||
|
||||
- [ ] **Step 6: Commit the overview/admin interaction work**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
git add web/storyforge-web-v4/index.html web/storyforge-web-v4/assets/app.js web/storyforge-web-v4/assets/storyforge-dashboard-home.js web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
git commit -m "feat: add dashboard tab flow and admin workbench entry"
|
||||
```
|
||||
|
||||
### Task 4: Add Styles, Docs, and Regression Coverage
|
||||
|
||||
**Files:**
|
||||
- Modify: `web/storyforge-web-v4/assets/styles.css`
|
||||
- Modify: `web/storyforge-web-v4/README.md`
|
||||
- Modify: `scripts/check_repo_baseline.sh`
|
||||
- Modify: `tests/test_production_baseline.py`
|
||||
|
||||
- [ ] **Step 1: Add a failing baseline regression test for the homepage redesign wiring**
|
||||
|
||||
Append to `tests/test_production_baseline.py`:
|
||||
|
||||
```python
|
||||
def test_baseline_script_covers_homepage_dashboard_node_test(self) -> None:
|
||||
script = (ROOT / "scripts" / "check_repo_baseline.sh").read_text(encoding="utf-8")
|
||||
self.assertIn("dashboard-home.test.mjs", script)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the Python regression test and verify the current branch fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
python3 -m unittest tests.test_production_baseline.ProductionBaselineTests.test_baseline_script_covers_homepage_dashboard_node_test -v
|
||||
```
|
||||
|
||||
Expected: FAIL before `scripts/check_repo_baseline.sh` is updated to run the homepage Node test.
|
||||
|
||||
- [ ] **Step 3: Add the new CSS and update docs/baseline script**
|
||||
|
||||
Modify `web/storyforge-web-v4/assets/styles.css` with homepage-specific classes:
|
||||
|
||||
```css
|
||||
.dashboard-context-row { display:flex; justify-content:space-between; gap:16px; flex-wrap:wrap; }
|
||||
.dashboard-context-chip { display:flex; align-items:center; gap:8px; border:1px solid var(--line); border-radius:14px; padding:10px 12px; background:var(--panel-soft); }
|
||||
.dashboard-priority-panel { display:grid; gap:12px; }
|
||||
.dashboard-action-primary { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:16px; align-items:center; }
|
||||
.dashboard-action-secondary-list { display:grid; gap:10px; }
|
||||
.dashboard-overview-tabs { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; }
|
||||
.dashboard-overview-tab.is-active { border-color: var(--accent); background: var(--accent-soft); }
|
||||
```
|
||||
|
||||
Modify `web/storyforge-web-v4/README.md`:
|
||||
|
||||
```md
|
||||
- 首页已切到“人类决策优先”结构:
|
||||
- 先显示当前项目与今日动作
|
||||
- 再显示项目概览 tab
|
||||
- 管理员配置台通过独立导航进入
|
||||
```
|
||||
|
||||
Modify `scripts/check_repo_baseline.sh`:
|
||||
|
||||
```sh
|
||||
echo "[5/5] validate homepage dashboard tests"
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the full redesign verification**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
python3 -m unittest tests.test_platform_contracts tests.test_production_baseline -v
|
||||
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs
|
||||
node --check web/storyforge-web-v4/assets/storyforge-dashboard-home.js
|
||||
node --check web/storyforge-web-v4/assets/app.js
|
||||
bash scripts/check_repo_baseline.sh
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- Python tests PASS
|
||||
- Node homepage test PASS
|
||||
- `baseline checks passed`
|
||||
- `git diff --check` returns no output
|
||||
|
||||
- [ ] **Step 5: Commit the styling and regression coverage**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/StoryForge-gitea
|
||||
git add web/storyforge-web-v4/assets/styles.css web/storyforge-web-v4/README.md scripts/check_repo_baseline.sh tests/test_production_baseline.py
|
||||
git commit -m "test: cover homepage dashboard redesign"
|
||||
```
|
||||
Reference in New Issue
Block a user