Files
storyforge/docs/superpowers/plans/2026-03-28-homepage-workbench-redesign.md
kris 65db3cd336
Some checks failed
StoryForge CI / Baseline checks (push) Has been cancelled
StoryForge CI / Backend tests (push) Has been cancelled
StoryForge CI / Web tests (push) Has been cancelled
chore: sync storyforge handoff state
2026-05-02 17:50:21 +08:00

25 KiB

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:

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:

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:

(function () {
  function defaultEscapeHtml(value) {
    return String(value ?? "")
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;")
      .replaceAll('"', "&quot;");
  }

  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:

<script src="./assets/storyforge-dashboard-home.js"></script>
<script src="./assets/app.js"></script>

Modify web/storyforge-web-v4/assets/app.js near renderDashboardScreen():

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:

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:

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:

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:

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:

  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:

const appState = {
  // existing fields...
  dashboardOverviewTab: "project_progress",
  dashboardActionReason: null
};

Build the raw dashboard inputs in web/storyforge-web-v4/assets/app.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:

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:

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:

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:

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:

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:

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:

<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:

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:

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:

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:

    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:

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:

.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:

- 首页已切到“人类决策优先”结构:
  - 先显示当前项目与今日动作
  - 再显示项目概览 tab
  - 管理员配置台通过独立导航进入

Modify scripts/check_repo_baseline.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:

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:

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"