Players list view: SSR table at /players #4

Open
opened 2026-04-25 21:59:28 +00:00 by forgejo_admin · 2 comments

Type

Feature

Lineage

First end-to-end user-visible feature. Hard depends on forgejo_admin/westside-admin#6 (scaffolding), #1 (Drizzle), #2 (Auth), #3 (Tenant helper). Indirect dep on forgejo_admin/pal-e-platform#301 (Keycloak client) + #302 (Postgres user) since #2 and #1 depend on those.

Repo

forgejo_admin/westside-admin

User Story

story-westside-admin-admin-row-crud. The READ half of the core story — concrete on the players table first per locked decision (validate the stack on one table before generalizing).

Context

First end-to-end seam: auth → tenant scoping → Drizzle query → SSR render. Picks the players table because it's the widest (37 columns), most actively edited, and most representative — if the stack handles players cleanly it'll handle the other 21 tables.

UI is intentionally minimal in v1: paginated table, filter by name, type-aware rendering (enums as colored badges, dates formatted, jsonb collapsed). NO generic introspection-driven table component yet — that's a later iteration once the seam is proven.

File Targets

Create:

  • src/routes/players/+page.server.tsload() queries scopedDb.select().from(players) with pagination + name filter
  • src/routes/players/+page.svelte — renders typed data.players as <table>, pagination links, filter input
  • src/lib/components/EnumBadge.svelte — renders a string with a color derived from the value (consistent across renders)
  • src/lib/components/DateCell.svelte — formats timestamp as YYYY-MM-DD HH:mm
  • src/lib/components/JsonbCell.svelte — collapsed by default, click to expand to <pre>

Modify:

  • src/routes/+layout.svelte — add nav link to /players

Do NOT create:

  • Generic table component — that's the post-v1 iteration
  • Edit functionality — that's the next ticket (#5)
  • Other table routes — start with players only

Acceptance Criteria

  • GET /players shows the first 50 players (oldest first)
  • Pagination links: ?page=2, ?page=3, etc. — 50 per page
  • ?name=foo filter does a case-insensitive partial match on players.name
  • Enum columns (division, contract_status, jersey_option, etc.) render as colored badges
  • Date columns render formatted, not raw ISO
  • jsonb columns (contract_overrides) render collapsed
  • Sensitive columns (contract_token) are EXCLUDED from the table — handled at the +page.server.ts level via column allowlist
  • Anonymous request still 302s to Keycloak (auth from #2)
  • Non-admin still gets 403 (auth from #2)

Test Expectations

  • Integration: load /players as admin, assert 50 rows rendered + pagination
  • Integration: /players?name=Marcus returns rows where name contains "Marcus" (case-insensitive)
  • Unit: EnumBadge / DateCell / JsonbCell render correctly for sample inputs
  • Manual: visual check on real DB — Lucas's actual player list looks right
  • Test runner: whatever scaffolding #6 establishes (likely vitest)

Constraints

  • All queries through scopedDb (no direct db import — would fail lint from #3)
  • No client-side fetching — this is SSR only
  • Pure CSS, no Tailwind per feedback_no_tailwind
  • Sensitive column allowlist defined inline in +page.server.ts; refactor to shared config when 2nd table arrives (don't pre-abstract)

Checklist

  • PR opened
  • Visual check screenshot in PR description
  • Lighthouse / accessibility pass on the table
  • project-westside-admin
  • arch-dataflow-westside-admin Flow 1
  • arch-domain-westside-admin (players entity)
  • DEPENDS ON: #6, #1, #2, #3
### Type Feature ### Lineage First end-to-end user-visible feature. Hard depends on `forgejo_admin/westside-admin#6` (scaffolding), `#1` (Drizzle), `#2` (Auth), `#3` (Tenant helper). Indirect dep on `forgejo_admin/pal-e-platform#301` (Keycloak client) + `#302` (Postgres user) since #2 and #1 depend on those. ### Repo `forgejo_admin/westside-admin` ### User Story `story-westside-admin-admin-row-crud`. The READ half of the core story — concrete on the `players` table first per locked decision (validate the stack on one table before generalizing). ### Context First end-to-end seam: auth → tenant scoping → Drizzle query → SSR render. Picks the `players` table because it's the widest (37 columns), most actively edited, and most representative — if the stack handles `players` cleanly it'll handle the other 21 tables. UI is intentionally minimal in v1: paginated table, filter by name, type-aware rendering (enums as colored badges, dates formatted, jsonb collapsed). NO generic introspection-driven table component yet — that's a later iteration once the seam is proven. ### File Targets Create: - `src/routes/players/+page.server.ts` — `load()` queries `scopedDb.select().from(players)` with pagination + name filter - `src/routes/players/+page.svelte` — renders typed `data.players` as `<table>`, pagination links, filter input - `src/lib/components/EnumBadge.svelte` — renders a string with a color derived from the value (consistent across renders) - `src/lib/components/DateCell.svelte` — formats timestamp as `YYYY-MM-DD HH:mm` - `src/lib/components/JsonbCell.svelte` — collapsed by default, click to expand to `<pre>` Modify: - `src/routes/+layout.svelte` — add nav link to `/players` Do NOT create: - Generic table component — that's the post-v1 iteration - Edit functionality — that's the next ticket (#5) - Other table routes — start with players only ### Acceptance Criteria - [ ] GET `/players` shows the first 50 players (oldest first) - [ ] Pagination links: `?page=2`, `?page=3`, etc. — 50 per page - [ ] `?name=foo` filter does a case-insensitive partial match on `players.name` - [ ] Enum columns (division, contract_status, jersey_option, etc.) render as colored badges - [ ] Date columns render formatted, not raw ISO - [ ] jsonb columns (contract_overrides) render collapsed - [ ] Sensitive columns (contract_token) are EXCLUDED from the table — handled at the +page.server.ts level via column allowlist - [ ] Anonymous request still 302s to Keycloak (auth from #2) - [ ] Non-admin still gets 403 (auth from #2) ### Test Expectations - Integration: load `/players` as admin, assert 50 rows rendered + pagination - Integration: `/players?name=Marcus` returns rows where name contains "Marcus" (case-insensitive) - Unit: EnumBadge / DateCell / JsonbCell render correctly for sample inputs - Manual: visual check on real DB — Lucas's actual player list looks right - Test runner: whatever scaffolding `#6` establishes (likely `vitest`) ### Constraints - All queries through `scopedDb` (no direct `db` import — would fail lint from #3) - No client-side fetching — this is SSR only - Pure CSS, no Tailwind per `feedback_no_tailwind` - Sensitive column allowlist defined inline in +page.server.ts; refactor to shared config when 2nd table arrives (don't pre-abstract) ### Checklist - [ ] PR opened - [ ] Visual check screenshot in PR description - [ ] Lighthouse / accessibility pass on the table ### Related - `project-westside-admin` - `arch-dataflow-westside-admin` Flow 1 - `arch-domain-westside-admin` (players entity) - DEPENDS ON: `#6`, `#1`, `#2`, `#3`
Author
Owner

Scope Review: NEEDS_REFINEMENT

Review note: review-1092-2026-04-25

Scope, traceability, and template completeness are solid. Story note + arch backing notes verified. Refinement is on dependency-traceability hygiene only — no structural rewrite needed.

Issues to address:

  • [BODY] Lineage names blockers in prose ("scaffolding + Drizzle + auth + tenant helper") but does not list specific issue numbers. Add: "Blocked by westside-admin#6 (sveltekit scaffolding), #1 + #3 (scopedDb / tenant helper), #2 (hooks-server auth), and pal-e-platform#301 (Keycloak), #302 (postgres-grants)."
  • [SCOPE] Sequencing: 6 upstream tickets must merge before this ticket can move past next_up. Hold in todo after refinement.
  • [BODY] Optional: name the test runner once scaffolding (#6) lands (likely npm test / vitest).

Decomposition: not needed. 5 new files + 1 modify is at the upper edge of the 5-minute rule but forms a single coherent SSR seam (server load ↔ page render ↔ cell components are mutually dependent). Splitting would fragment the contract.

arch:page-server label resolves to arch-dataflow-westside-admin Flow 1 (referenced in Related). No arch-page-server note expected — page-server is a SvelteKit primitive, not a domain component.

## Scope Review: NEEDS_REFINEMENT Review note: `review-1092-2026-04-25` Scope, traceability, and template completeness are solid. Story note + arch backing notes verified. Refinement is on dependency-traceability hygiene only — no structural rewrite needed. Issues to address: - `[BODY]` Lineage names blockers in prose ("scaffolding + Drizzle + auth + tenant helper") but does not list specific issue numbers. Add: "Blocked by westside-admin#6 (sveltekit scaffolding), #1 + #3 (scopedDb / tenant helper), #2 (hooks-server auth), and pal-e-platform#301 (Keycloak), #302 (postgres-grants)." - `[SCOPE]` Sequencing: 6 upstream tickets must merge before this ticket can move past `next_up`. Hold in `todo` after refinement. - `[BODY]` Optional: name the test runner once scaffolding (#6) lands (likely `npm test` / vitest). Decomposition: not needed. 5 new files + 1 modify is at the upper edge of the 5-minute rule but forms a single coherent SSR seam (server load ↔ page render ↔ cell components are mutually dependent). Splitting would fragment the contract. `arch:page-server` label resolves to `arch-dataflow-westside-admin` Flow 1 (referenced in Related). No `arch-page-server` note expected — page-server is a SvelteKit primitive, not a domain component.
Author
Owner

Scope Review v2: APPROVED

Review note: review-1092-2026-04-25-v2

Both [BODY] gaps from v1 (review-1092-2026-04-25) are resolved:

  • Lineage now enumerates explicit blocker issue numbers (#6, #1, #2, #3, plus indirect pal-e-platform#301, #302)
  • Test runner pinned to whatever scaffolding #6 establishes (likely vitest)

No structural changes. Ticket is READY for backlog -> todo. Operational note: hold in todo until all 6 blockers merge before promoting to next_up.

## Scope Review v2: APPROVED Review note: `review-1092-2026-04-25-v2` Both [BODY] gaps from v1 (`review-1092-2026-04-25`) are resolved: - Lineage now enumerates explicit blocker issue numbers (#6, #1, #2, #3, plus indirect pal-e-platform#301, #302) - Test runner pinned to whatever scaffolding #6 establishes (likely vitest) No structural changes. Ticket is READY for backlog -> todo. Operational note: hold in `todo` until all 6 blockers merge before promoting to `next_up`.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/westside-admin#4
No description provided.