syntaxai/tdd.md · main · src / b51_render_projects.ts

b51_render_projects.ts 134 lines · 5368 bytes raw
// c51 (projects) — body builders for /projects, /projects/new,
// /projects/:owner/:repo. Imports chrome helpers from c51_render_layout.

import type { ProjectRow } from "./a31_project_config.ts";
import { PROJECT_CONFIG_PATH } from "./a31_project_config.ts";
import { escape } from "./b51_render_layout.ts";

const projectListRow = (p: ProjectRow): string => {
  const slug = `${p.repoOwner}/${p.repoName}`;
  const display = p.displayName ?? slug;
  const team = p.team ? ` <span class="muted">· ${escape(p.team)}</span>` : "";
  const branches = p.trackedBranches.map((b) => `\`${b}\``).join(", ");
  const runner = p.testRunner === "none" ? "trace-only" : p.testRunner;
  return `| [${escape(display)}](/projects/${p.repoOwner}/${p.repoName}) ${team} | ${branches} | ${runner} |`;
};

export const projectsLandingMd = (projects: ProjectRow[]): string => {
  const rows = projects.length === 0
    ? `| _no projects yet — [register one](/projects/new)_ | | |`
    : projects.map(projectListRow).join("\n");
  return `# projects

> Real repos that opted in to tdd.md scoring. Each project drops \`${PROJECT_CONFIG_PATH}\` at its root, registers here, and from then on its commits on tracked branches get judged structurally — red-fails, green-passes, no test-deletion, no regression. The aggregated scores feed [the reports](/reports).

## tracked

| project | branches | runner |
|---|---|---|
${rows}

## register a repo

[Register a project →](/projects/new) — paste a public GitHub URL; tdd.md fetches \`${PROJECT_CONFIG_PATH}\` from the default branch and onboards it.

## the config file

Drop \`${PROJECT_CONFIG_PATH}\` at the root of your repo's default branch:

\`\`\`json
{
  "version": 1,
  "test_runner": "none",
  "tracked_branches": ["main"],
  "display_name": "API Gateway",
  "team": "platform"
}
\`\`\`

- **\`test_runner\`** — \`"none"\` for trace-mode (commit-discipline only, language-agnostic). \`"bun"\` will run the test suite once the sandbox-runner ships.
- **\`tracked_branches\`** — pushes to these branches get scored. Defaults to \`["main"]\`.
- **\`display_name\`** / **\`team\`** — optional, only used in the reporting UI.

## what comes next

Registration just stores the project. Per-commit judging (the part that produces score data for the reports) lands in the next sliver — until then the [report pages](/reports) keep showing the demo dataset.

[← back to tdd.md](/) · [the reports](/reports)
`;
};

export const projectRegisterMd = (
  viewer: string | null,
  prefilled?: string,
  errorMessage?: string,
): string => {
  if (!viewer) {
    return `# register a project

> You need to sign in before registering a project. We use your GitHub identity to record who onboarded the repo.

[ sign in with github → ](/auth/github/start)

[← all projects](/projects)
`;
  }
  const error = errorMessage
    ? `<div class="project-form-error"><strong>Couldn't register that repo:</strong><br>${escape(errorMessage)}</div>`
    : "";
  const value = prefilled ? ` value="${escape(prefilled)}"` : "";
  return `# register a project

> Paste a public GitHub URL. tdd.md fetches \`${PROJECT_CONFIG_PATH}\` from its default branch, validates it, and onboards the repo. Re-register the same repo to refresh the config.

${error}

<form method="post" action="/projects/new" class="project-form">
  <label for="repo-url">Repository URL or <code>owner/name</code></label>
  <input id="repo-url" name="repo" type="text" required
    placeholder="https://github.com/owner/name"
    autocomplete="off" autocapitalize="off" autocorrect="off"${value} />
  <button type="submit">Register</button>
</form>

> Signed in as <code>${escape(viewer)}</code>. Don't have \`${PROJECT_CONFIG_PATH}\` yet? [See the format on /projects](/projects#the-config-file).

[← all projects](/projects)
`;
};

export const projectDetailMd = (p: ProjectRow): string => {
  const display = p.displayName ?? `${p.repoOwner}/${p.repoName}`;
  const registeredAt = new Date(p.registeredAt).toISOString().slice(0, 10);
  const branches = p.trackedBranches.map((b) => `\`${b}\``).join(", ");
  const runnerNote = p.testRunner === "none"
    ? "Trace-mode — judging looks at commit phase tags, test-count drift, and refactor stability. No test execution."
    : "Bun runner — test suite executes in a sandbox at every tracked-branch commit. (Sandbox-runner ships in the next sliver; meanwhile this falls back to trace-mode.)";
  return `# ${escape(display)}

> [${escape(p.repoOwner)}/${escape(p.repoName)}](https://github.com/${p.repoOwner}/${p.repoName}) · registered by [${escape(p.registeredBy)}](/agents/${p.registeredBy}) on ${registeredAt}.

## config

| key | value |
|---|---|
| test_runner | \`${p.testRunner}\` |
| tracked_branches | ${branches} |
| display_name | ${p.displayName ? `\`${escape(p.displayName)}\`` : "_(none)_"} |
| team | ${p.team ? `\`${escape(p.team)}\`` : "_(none)_"} |
| status | \`${p.status}\` |

${runnerNote}

## scored commits

> _No commits judged yet._ The webhook ingest + judging pipeline lands in the next sliver — once it does, scored commits for tracked branches will appear here grouped by agent.

## refresh

Push an updated \`${PROJECT_CONFIG_PATH}\` to your default branch and [re-register](/projects/new?repo=${encodeURIComponent(`${p.repoOwner}/${p.repoName}`)}) to pick up the new config.

[← all projects](/projects)
`;
};