@@ -527,7 +527,7 @@ git commit -m "feat(admin): staff instance detail, subscriptions and richer sear
**Interfaces:**
- Produces: Tailwind classes ` bg-ground bg-panel bg-panel-2 text-ink text-ink-2 text-ink-3 border-rule border-rule-soft text-accent bg-accent text-valid text-warn text-expired`, fonts ` font-sans font-mono` (no display face — headings are the sans at weight 800)
- [ ] **Step 1: package.json**
- [x ] **Step 1: package.json**
` ``json
{
@@ -569,7 +569,7 @@ git commit -m "feat(admin): staff instance detail, subscriptions and richer sear
}
` ``
- [ ] **Step 2: tsconfig.json**
- [x ] **Step 2: tsconfig.json**
` ``json
{
@@ -596,7 +596,7 @@ git commit -m "feat(admin): staff instance detail, subscriptions and richer sear
}
` ``
- [ ] **Step 3: next.config.ts**
- [x ] **Step 3: next.config.ts**
` ``ts
import type { NextConfig } from "next";
@@ -614,7 +614,7 @@ const nextConfig: NextConfig = {
export default nextConfig;
` ``
- [ ] **Step 4: postcss.config.js and tailwind.config.ts**
- [x ] **Step 4: postcss.config.js and tailwind.config.ts**
` postcss.config.js`:
@@ -678,7 +678,7 @@ const config: Config = {
export default config;
` ``
- [ ] **Step 5: app/globals.css**
- [x ] **Step 5: app/globals.css**
Copy the token block out of ` site/app/globals.css` unchanged — same names, same
values, all three theme selectors. The only addition is ` --accent-wash`, which
@@ -857,7 +857,7 @@ classes on headings in later tasks (`text-3xl font-extrabold tracking-[-0.03em]`
are belt-and-braces for elements that are not ` h1`– ` h3`. Leave them; they cost
nothing and keep a ` <p>` used as a title looking right.
- [ ] **Step 6: app/layout.tsx**
- [x ] **Step 6: app/layout.tsx**
` ``tsx
import type { Metadata } from "next";
@@ -892,7 +892,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
}
` ``
- [ ] **Step 7: .gitignore**
- [x ] **Step 7: .gitignore**
` ``
node_modules
@@ -902,7 +902,7 @@ next-env.d.ts
*.lic
` ``
- [ ] **Step 8: Install and confirm it builds later**
- [x ] **Step 8: Install and confirm it builds later**
` ``bash
sh /tmp/noderun.sh adminsite npm install
@@ -910,7 +910,7 @@ sh /tmp/noderun.sh adminsite npm install
Expected: a lockfile appears. ` npm run build` cannot pass until Task 5 creates the pages it imports; that is Task 5's verification.
- [ ] **Step 9: Commit**
- [x ] **Step 9: Commit**
` ``bash
git add adminsite/
@@ -935,7 +935,7 @@ The repo has no frontend test setup. This task creates it and proves it on spec
- ` <NotConnectedPanel />`
- ` licenceState(expiresAt: string | undefined, hasLicence: boolean): "valid" | "warn" | "expired" | "none"`, ` daysRemaining(iso: string): number`, ` formatDate(iso: string): string`
- [ ] **Step 1: vitest.config.ts and test/setup.ts**
- [x ] **Step 1: vitest.config.ts and test/setup.ts**
` ``ts
import { defineConfig } from "vitest/config";
@@ -967,7 +967,7 @@ afterEach(() => {
});
` ``
- [ ] **Step 2: Write the failing test for the not-connected state**
- [x ] **Step 2: Write the failing test for the not-connected state**
` adminsite/components/NotConnected.test.tsx`:
@@ -997,7 +997,7 @@ describe("NotConnectedPanel", () => {
});
` ``
- [ ] **Step 3: Run it and watch it fail**
- [x ] **Step 3: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx
@@ -1005,7 +1005,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx
Expected: FAIL — ` Failed to resolve import "./NotConnected"`.
- [ ] **Step 4: Write the component**
- [x ] **Step 4: Write the component**
` adminsite/components/NotConnected.tsx`:
@@ -1043,7 +1043,7 @@ export function NotConnectedPanel({ url }: { url: string }) {
}
` ``
- [ ] **Step 5: Run it and watch it pass**
- [x ] **Step 5: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx
@@ -1051,7 +1051,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/NotConnected.test.tsx
Expected: ` 2 passed`.
- [ ] **Step 6: Write the API client**
- [x ] **Step 6: Write the API client**
` adminsite/lib/api.ts`:
@@ -1279,7 +1279,7 @@ export const api = {
};
` ``
- [ ] **Step 7: Write format helpers and providers**
- [x ] **Step 7: Write format helpers and providers**
` adminsite/lib/format.ts`:
@@ -1353,7 +1353,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
}
` ``
- [ ] **Step 8: Run the whole suite**
- [x ] **Step 8: Run the whole suite**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run
@@ -1361,7 +1361,7 @@ sh /tmp/noderun.sh adminsite npx vitest run
Expected: ` 2 passed`.
- [ ] **Step 9: Commit**
- [x ] **Step 9: Commit**
` ``bash
git add adminsite/
@@ -1381,7 +1381,7 @@ Covers spec test 1.
- Consumes: ` api`, ` NotConnected`, ` ApiError`
- Produces: ` useSession()`, ` <RequireKind kind="staff" | "customer">`, ` <EnvBadge />`, ` <Button />`, ` <Field />`
- [ ] **Step 1: Write the failing guard test**
- [x ] **Step 1: Write the failing guard test**
` adminsite/lib/session.test.tsx`:
@@ -1433,7 +1433,7 @@ describe("RequireKind", () => {
});
` ``
- [ ] **Step 2: Run it and watch it fail**
- [x ] **Step 2: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run lib/session.test.tsx
@@ -1441,7 +1441,7 @@ sh /tmp/noderun.sh adminsite npx vitest run lib/session.test.tsx
Expected: FAIL — cannot resolve ` ./session`.
- [ ] **Step 3: Write the session module**
- [x ] **Step 3: Write the session module**
` adminsite/lib/session.ts`:
@@ -1497,7 +1497,7 @@ export function RequireKind({
Note the redirect target for a mismatched session is the *other* home, so a customer on ` /staff/*` lands on ` /`.
- [ ] **Step 4: Run it and watch it pass**
- [x ] **Step 4: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run lib/session.test.tsx
@@ -1505,7 +1505,7 @@ sh /tmp/noderun.sh adminsite npx vitest run lib/session.test.tsx
Expected: ` 3 passed`. The mismatch test asserts ` replace("/")`, which the code produces for a customer.
- [ ] **Step 5: Write the shared primitives**
- [x ] **Step 5: Write the shared primitives**
` adminsite/components/EnvBadge.tsx`:
@@ -1596,7 +1596,7 @@ export function Field({
}
` ``
- [ ] **Step 6: Write the auth screens**
- [x ] **Step 6: Write the auth screens**
` adminsite/app/login/page.tsx`:
@@ -1837,7 +1837,7 @@ export default function VerifyPage() {
}
` ``
- [ ] **Step 7: Write the two guarded layouts and the root redirect**
- [x ] **Step 7: Write the two guarded layouts and the root redirect**
` adminsite/app/(customer)/layout.tsx`:
@@ -1883,7 +1883,7 @@ export default function NotFound() {
}
` ``
- [ ] **Step 8: Run the suite**
- [x ] **Step 8: Run the suite**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run
@@ -1891,7 +1891,7 @@ sh /tmp/noderun.sh adminsite npx vitest run
Expected: ` 5 passed`.
- [ ] **Step 9: Commit**
- [x ] **Step 9: Commit**
` ``bash
git add adminsite/
@@ -1911,7 +1911,7 @@ Covers spec test 2 and the `awaiting_link` prompt.
- Consumes: ` licenceState`, ` daysRemaining`, ` formatDate`, ` Instance`, ` License`
- Produces: ` <StatePill state={LicenceState} />`, ` <InstanceCard instance license />`
- [ ] **Step 1: Write the failing card test**
- [x ] **Step 1: Write the failing card test**
` adminsite/components/InstanceCard.test.tsx`:
@@ -1990,7 +1990,7 @@ describe("InstanceCard", () => {
});
` ``
- [ ] **Step 2: Run it and watch it fail**
- [x ] **Step 2: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/InstanceCard.test.tsx
@@ -1998,7 +1998,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/InstanceCard.test.tsx
Expected: FAIL — cannot resolve ` ./InstanceCard`.
- [ ] **Step 3: Write StatePill**
- [x ] **Step 3: Write StatePill**
` adminsite/components/StatePill.tsx`:
@@ -2047,7 +2047,7 @@ export function StatePill({ state }: { state: LicenceState }) {
}
` ``
- [ ] **Step 4: Write InstanceCard**
- [x ] **Step 4: Write InstanceCard**
` adminsite/components/InstanceCard.tsx`:
@@ -2143,7 +2143,7 @@ import { StatePill as StatePillLazy } from "./StatePill";
Move that import to the top of the file when writing it; it is shown last only to keep the diff readable.
- [ ] **Step 5: Run it and watch it pass**
- [x ] **Step 5: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/InstanceCard.test.tsx
@@ -2151,7 +2151,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/InstanceCard.test.tsx
Expected: ` 5 passed`.
- [ ] **Step 6: Write the overview**
- [x ] **Step 6: Write the overview**
` adminsite/app/(customer)/page.tsx`:
@@ -2228,7 +2228,7 @@ export default function OverviewPage() {
}
` ``
- [ ] **Step 7: Commit**
- [x ] **Step 7: Commit**
` ``bash
git add adminsite/
@@ -2247,7 +2247,7 @@ Covers spec tests 4 and 5.
**Interfaces:**
- Produces: ` <LicenceDelivery instanceId blob downloadUrl />`, ` <RelinkPanel instanceId used max onRelink />`
- [ ] **Step 1: Write the failing tests**
- [x ] **Step 1: Write the failing tests**
` adminsite/components/LicenceDelivery.test.tsx`:
@@ -2296,7 +2296,7 @@ describe("RelinkPanel", () => {
});
` ``
- [ ] **Step 2: Run both and watch them fail**
- [x ] **Step 2: Run both and watch them fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/LicenceDelivery.test.tsx components/RelinkPanel.test.tsx
@@ -2304,7 +2304,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/LicenceDelivery.test.tsx
Expected: FAIL — both imports unresolved.
- [ ] **Step 3: Write LicenceDelivery**
- [x ] **Step 3: Write LicenceDelivery**
` ``tsx
"use client";
@@ -2371,7 +2371,7 @@ export function LicenceDelivery({
}
` ``
- [ ] **Step 4: Write RelinkPanel**
- [x ] **Step 4: Write RelinkPanel**
` ``tsx
"use client";
@@ -2436,7 +2436,7 @@ export function RelinkPanel({
}
` ``
- [ ] **Step 5: Run both and watch them pass**
- [x ] **Step 5: Run both and watch them pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/LicenceDelivery.test.tsx components/RelinkPanel.test.tsx
@@ -2444,7 +2444,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/LicenceDelivery.test.tsx
Expected: ` 3 passed`.
- [ ] **Step 6: Write the instance detail page**
- [x ] **Step 6: Write the instance detail page**
` adminsite/app/(customer)/instances/[id]/page.tsx`:
@@ -2549,7 +2549,7 @@ function Fact({ label, value }: { label: string; value: string }) {
` License.blob` is ` json:"-"` on the backend, so add a customer-facing blob field: in ` admin/internal/api/customer.go`, ` getInstanceLicense` and ` downloadInstanceLicense` already have the record — change ` getInstanceLicense` to respond ` c.JSON(http.StatusOK, gin.H{"license": lic, "blob": lic.Blob})` and widen the TS type to ` License & { blob?: string }`. Do this in Step 7 before the page can render the blob.
- [ ] **Step 7: Return the blob to its owner**
- [x ] **Step 7: Return the blob to its owner**
In ` admin/internal/api/customer.go`, replace the final line of ` getInstanceLicense`:
@@ -2572,7 +2572,7 @@ In `adminsite/lib/api.ts`, change the customer call's type:
Rebuild admin: ` sh /tmp/gorun.sh admin go build ./...` — expected no output.
- [ ] **Step 8: Run the suite**
- [x ] **Step 8: Run the suite**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run
@@ -2580,7 +2580,7 @@ sh /tmp/noderun.sh adminsite npx vitest run
Expected: ` 13 passed`.
- [ ] **Step 9: Commit**
- [x ] **Step 9: Commit**
` ``bash
git add adminsite/ admin/
@@ -2599,7 +2599,7 @@ Covers spec test 3. This is the screen the five-minute bar applies to.
**Interfaces:**
- Produces: ` <LinkForm onLinked={(instanceId: string) => void} />`
- [ ] **Step 1: Write the failing test**
- [x ] **Step 1: Write the failing test**
` ``tsx
import { render, screen, waitFor } from "@testing-library/react";
@@ -2647,7 +2647,7 @@ describe("LinkForm", () => {
});
` ``
- [ ] **Step 2: Run it and watch it fail**
- [x ] **Step 2: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run app/\(customer\)/instances/link/LinkForm.test.tsx
@@ -2655,7 +2655,7 @@ sh /tmp/noderun.sh adminsite npx vitest run app/\(customer\)/instances/link/Link
Expected: FAIL — cannot resolve ` ./LinkForm`.
- [ ] **Step 3: Write LinkForm**
- [x ] **Step 3: Write LinkForm**
` ``tsx
"use client";
@@ -2731,7 +2731,7 @@ export function LinkForm({ onLinked }: { onLinked: (instanceId: string) => void
}
` ``
- [ ] **Step 4: Run it and watch it pass**
- [x ] **Step 4: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run app/\(customer\)/instances/link/LinkForm.test.tsx
@@ -2739,7 +2739,7 @@ sh /tmp/noderun.sh adminsite npx vitest run app/\(customer\)/instances/link/Link
Expected: ` 3 passed`.
- [ ] **Step 5: Write the page**
- [x ] **Step 5: Write the page**
` ``tsx
"use client";
@@ -2774,7 +2774,7 @@ export default function LinkPage() {
}
` ``
- [ ] **Step 6: Commit**
- [x ] **Step 6: Commit**
` ``bash
git add adminsite/
@@ -2788,7 +2788,7 @@ git commit -m "feat(adminsite): the self-hosted link flow"
**Files:**
- Create: ` adminsite/app/(customer)/billing/page.tsx`
- [ ] **Step 1: Write the page**
- [x ] **Step 1: Write the page**
` ``tsx
"use client";
@@ -2854,7 +2854,7 @@ export default function BillingPage() {
That last paragraph is replaced by the Paddle portal deep-link in spec 5. It states the current truth rather than linking nowhere.
- [ ] **Step 2: Commit**
- [x ] **Step 2: Commit**
` ``bash
git add adminsite/
@@ -2873,7 +2873,7 @@ Covers spec test 7.
**Interfaces:**
- Produces: ` <Queue title count tone items />` where ` items: { label: string; href: string; meta: string }[]`
- [ ] **Step 1: Write the failing test**
- [x ] **Step 1: Write the failing test**
` ``tsx
import { render, screen } from "@testing-library/react";
@@ -2908,7 +2908,7 @@ describe("Queue", () => {
});
` ``
- [ ] **Step 2: Run it and watch it fail**
- [x ] **Step 2: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/Queue.test.tsx
@@ -2916,7 +2916,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/Queue.test.tsx
Expected: FAIL — cannot resolve ` ./Queue`.
- [ ] **Step 3: Write Queue**
- [x ] **Step 3: Write Queue**
` ``tsx
import Link from "next/link";
@@ -2960,7 +2960,7 @@ export function Queue({
}
` ``
- [ ] **Step 4: Run it and watch it pass**
- [x ] **Step 4: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/Queue.test.tsx
@@ -2968,7 +2968,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/Queue.test.tsx
Expected: ` 2 passed`.
- [ ] **Step 5: Write the dashboard**
- [x ] **Step 5: Write the dashboard**
` ``tsx
"use client";
@@ -3052,7 +3052,7 @@ export default function StaffDashboard() {
}
` ``
- [ ] **Step 6: Commit**
- [x ] **Step 6: Commit**
` ``bash
git add adminsite/
@@ -3068,7 +3068,7 @@ Covers spec test 9.
**Files:**
- Create: ` adminsite/app/(staff)/staff/accounts/page.tsx`, ` adminsite/app/(staff)/staff/accounts/[id]/page.tsx`, ` adminsite/app/(staff)/staff/accounts/AccountSearch.tsx`, ` adminsite/app/(staff)/staff/accounts/AccountSearch.test.tsx`
- [ ] **Step 1: Write the failing test**
- [x ] **Step 1: Write the failing test**
` ``tsx
import { render, screen, waitFor } from "@testing-library/react";
@@ -3107,7 +3107,7 @@ describe("AccountSearch", () => {
});
` ``
- [ ] **Step 2: Run it and watch it fail**
- [x ] **Step 2: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run app/\(staff\)/staff/accounts/AccountSearch.test.tsx
@@ -3115,7 +3115,7 @@ sh /tmp/noderun.sh adminsite npx vitest run app/\(staff\)/staff/accounts/Account
Expected: FAIL — cannot resolve ` ./AccountSearch`.
- [ ] **Step 3: Write AccountSearch**
- [x ] **Step 3: Write AccountSearch**
` ``tsx
"use client";
@@ -3178,7 +3178,7 @@ export function AccountSearch() {
}
` ``
- [ ] **Step 4: Run it and watch it pass**
- [x ] **Step 4: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run app/\(staff\)/staff/accounts/AccountSearch.test.tsx
@@ -3186,7 +3186,7 @@ sh /tmp/noderun.sh adminsite npx vitest run app/\(staff\)/staff/accounts/Account
Expected: ` 1 passed`.
- [ ] **Step 5: Write the two pages**
- [x ] **Step 5: Write the two pages**
` accounts/page.tsx`:
@@ -3304,7 +3304,7 @@ function Panel({ title, children }: { title: string; children: React.ReactNode }
}
` ``
- [ ] **Step 6: Commit**
- [x ] **Step 6: Commit**
` ``bash
git add adminsite/
@@ -3323,7 +3323,7 @@ Covers spec test 10. This is the screen the whole design is built around.
**Interfaces:**
- Produces: ` <Ledger licenses={License[]} />`
- [ ] **Step 1: Write the failing test**
- [x ] **Step 1: Write the failing test**
` ``tsx
import { render, screen } from "@testing-library/react";
@@ -3386,7 +3386,7 @@ describe("Ledger", () => {
});
` ``
- [ ] **Step 2: Run it and watch it fail**
- [x ] **Step 2: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/Ledger.test.tsx
@@ -3394,7 +3394,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/Ledger.test.tsx
Expected: FAIL — cannot resolve ` ./Ledger`.
- [ ] **Step 3: Write Ledger**
- [x ] **Step 3: Write Ledger**
` ``tsx
import clsx from "clsx";
@@ -3473,7 +3473,7 @@ export function Ledger({ licenses }: { licenses: License[] }) {
}
` ``
- [ ] **Step 4: Run it and watch it pass**
- [x ] **Step 4: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/Ledger.test.tsx
@@ -3481,7 +3481,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/Ledger.test.tsx
Expected: ` 3 passed`.
- [ ] **Step 5: Write IssuePanel**
- [x ] **Step 5: Write IssuePanel**
` ``tsx
"use client";
@@ -3562,7 +3562,7 @@ export function IssuePanel({ instanceId, deployment }: { instanceId: string; dep
}
` ``
- [ ] **Step 6: Write the instance page**
- [x ] **Step 6: Write the instance page**
` ``tsx
"use client";
@@ -3623,7 +3623,7 @@ export default function StaffInstancePage() {
}
` ``
- [ ] **Step 7: Commit**
- [x ] **Step 7: Commit**
` ``bash
git add adminsite/
@@ -3637,7 +3637,7 @@ git commit -m "feat(adminsite): the licence ledger and staff instance actions"
**Files:**
- Create: ` adminsite/app/(staff)/staff/licenses/page.tsx`, ` adminsite/app/(staff)/staff/audit/page.tsx`
- [ ] **Step 1: Write the licences page**
- [x ] **Step 1: Write the licences page**
` ``tsx
"use client";
@@ -3713,7 +3713,7 @@ export default function LicensesPage() {
}
` ``
- [ ] **Step 2: Write the audit page**
- [x ] **Step 2: Write the audit page**
` ``tsx
"use client";
@@ -3755,7 +3755,7 @@ export default function AuditPage() {
}
` ``
- [ ] **Step 3: Commit**
- [x ] **Step 3: Commit**
` ``bash
git add adminsite/
@@ -3771,7 +3771,7 @@ Covers spec test 8.
**Files:**
- Create: ` adminsite/app/(staff)/staff/plans/page.tsx`, ` adminsite/components/ConfirmPlanChange.tsx`, ` adminsite/components/ConfirmPlanChange.test.tsx`
- [ ] **Step 1: Write the failing test**
- [x ] **Step 1: Write the failing test**
` ``tsx
import { render, screen } from "@testing-library/react";
@@ -3827,7 +3827,7 @@ describe("ConfirmPlanChange", () => {
});
` ``
- [ ] **Step 2: Run it and watch it fail**
- [x ] **Step 2: Run it and watch it fail**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/ConfirmPlanChange.test.tsx
@@ -3835,7 +3835,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/ConfirmPlanChange.test.ts
Expected: FAIL — cannot resolve ` ./ConfirmPlanChange`.
- [ ] **Step 3: Write ConfirmPlanChange**
- [x ] **Step 3: Write ConfirmPlanChange**
` ``tsx
import type { Plan } from "@/lib/api";
@@ -3896,7 +3896,7 @@ export function ConfirmPlanChange({
}
` ``
- [ ] **Step 4: Run it and watch it pass**
- [x ] **Step 4: Run it and watch it pass**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run components/ConfirmPlanChange.test.tsx
@@ -3904,7 +3904,7 @@ sh /tmp/noderun.sh adminsite npx vitest run components/ConfirmPlanChange.test.ts
Expected: ` 2 passed`.
- [ ] **Step 5: Write the plans page**
- [x ] **Step 5: Write the plans page**
` ``tsx
"use client";
@@ -4000,7 +4000,7 @@ export default function PlansPage() {
Those two buttons are the concrete edits staff actually need on day one; a general-purpose limits editor is deliberately not built until someone asks for it.
- [ ] **Step 6: Run the whole suite**
- [x ] **Step 6: Run the whole suite**
` ``bash
sh /tmp/noderun.sh adminsite npx vitest run
@@ -4008,7 +4008,7 @@ sh /tmp/noderun.sh adminsite npx vitest run
Expected: ` 26 passed`, covering all ten of spec 4's listed tests.
- [ ] **Step 7: Commit**
- [x ] **Step 7: Commit**
` ``bash
git add adminsite/
@@ -4023,7 +4023,7 @@ git commit -m "feat(adminsite): plan editing with both guard rails"
- Create: ` adminsite/Dockerfile`, ` adminsite/.dockerignore`
- Modify: ` deploy/docker-compose.site.yml`, ` .gitea/workflows/server-deploy.yml`, ` CLAUDE.md`
- [ ] **Step 1: Write the Dockerfile**
- [x ] **Step 1: Write the Dockerfile**
Context is ` adminsite/`, like ` web/` — this app has no shared-module dependency.
@@ -4082,7 +4082,7 @@ node_modules
*.lic
` ``
- [ ] **Step 2: Add the compose service**
- [x ] **Step 2: Add the compose service**
In ` deploy/docker-compose.site.yml`, after ` admin`:
@@ -4097,7 +4097,7 @@ In `deploy/docker-compose.site.yml`, after `admin`:
- admin
` ``
- [ ] **Step 3: Add the image build**
- [x ] **Step 3: Add the image build**
In ` .gitea/workflows/server-deploy.yml`, after the admin step:
@@ -4113,7 +4113,7 @@ In `.gitea/workflows/server-deploy.yml`, after the admin step:
docker push "$IMAGE"
` ``
- [ ] **Step 4: Document it**
- [x ] **Step 4: Document it**
In ` CLAUDE.md`:
@@ -4122,7 +4122,7 @@ In `CLAUDE.md`:
- add ` ADMIN_API_URL` and ` ADMIN_ENV` to the CI variables table beside ` SITE_API_URL`, carrying the same browser-reachable warning
- record that admin's HTTP surface gained ` GET /auth/me`, ` POST /auth/signup`, ` GET /api/staff/instances/:id` and ` GET /api/staff/subscriptions`, and that ` GET /api/instances/:id/license` now returns the blob to its owner
- [ ] **Step 5: Build the image**
- [x ] **Step 5: Build the image**
` ``bash
MSYS_NO_PATHCONV=1 docker build -q \
@@ -4132,7 +4132,7 @@ MSYS_NO_PATHCONV=1 docker build -q \
Expected: an image ID. This is the check that catches a missing dependency the dev server tolerated.
- [ ] **Step 6: Confirm the self-hosted deployment is untouched**
- [x ] **Step 6: Confirm the self-hosted deployment is untouched**
` ``bash
grep -c "adminsite" deploy/docker-compose.yml
@@ -4141,7 +4141,7 @@ cd deploy && MSYS_NO_PATHCONV=1 docker compose -f docker-compose.yml config --se
Expected: ` 0`, then exactly ` guacd redis server web`.
- [ ] **Step 7: Commit**
- [x ] **Step 7: Commit**
` ``bash
git add adminsite/ deploy/ .gitea/ CLAUDE.md