feat(adminsite): image, compose service, image build and docs
Publishes adminsite on 3004 -- 3000 is web, 3003 is the marketing site since the port shuffle -- and adds the sixth CI image. CLAUDE.md gains both new services. admin/ was never documented there at all: the backend plan's wiring task covered compose and CI but not the file every session actually loads. So this records admin's whole REST surface, its 404-not-403 rule, the three visual identities and, most importantly, that adminsite/ and site/ share one token set with nothing enforcing the match -- the same hazard shape as sitesvc's mirrored slug rules. Also notes that admin's REDIS_ADDR reaches admin only, because the base compose hardcodes redis:6379 for server. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user