Skip to content

feat(revamp): Block B — project updates (closes #39 #40 #41)#53

Draft
sacha-l wants to merge 3 commits intodevelopfrom
revamp/block-b-project-updates
Draft

feat(revamp): Block B — project updates (closes #39 #40 #41)#53
sacha-l wants to merge 3 commits intodevelopfrom
revamp/block-b-project-updates

Conversation

@sacha-l
Copy link
Copy Markdown
Collaborator

@sacha-l sacha-l commented Apr 22, 2026

Block B of the Phase 1 revamp — project updates.

Closes #39, #40, #41.

Block B journey slice (per spec §12)

"I can post an update on my project." Team member connects wallet on their project detail page → opens new Updates tab → clicks "Post update" → fills body → signs SIWS → update appears at the top of the list without a reload.

Commits

d746a45#39: schema + server API

  • Migration supabase/migrations/20260422010000_create_project_updates.sql (spec §4.3: UUID id, project_id FK, body CHECK length 1..2000, optional link_url, created_by, created_at; index on (project_id, created_at DESC)).
  • server/api/repositories/project-update.repository.js — listByProject (reverse-chron) + create.
  • server/api/services/project-update.service.js — thin wrap.
  • Extended project.controller.js with getProjectUpdates (public, 404 on missing project) and postProjectUpdate (422 on validation failure, trims body/linkUrl before validation, 201 on success).
  • m2-program.routes.jsGET /:projectId/updates (public) and POST /:projectId/updates (requireTeamMemberOrAdmin).
  • validation.jsvalidateProjectUpdate (body 1..2000, optional linkUrl via existing validateSimpleUrl).
  • auth.middleware.js — adds the /^Post an update to .+ on Stadium$/ pattern + a base statement.
  • 9 new unit tests (GET 404/empty/populated, POST 404/empty-body/too-long/bad-link/happy-path/linkUrl-trim).

fa3af45#40: Updates tab (read surface)

  • client/src/lib/api.tsApiProjectUpdate type + api.getProjectUpdates + api.postProjectUpdate (staged here; used by revamp-P1-06: Post Update modal (write surface, team-gated) #41).
  • client/src/lib/mockProjectUpdates.ts (new) — three seeded updates across Plata Mia (2) and Kleo Protocol (1) so preview mode shows both the empty state (most projects) and the populated state.
  • client/src/components/project/ProjectUpdatesTab.tsx (new) — loading, error, empty (warm builder-voice copy), populated (reverse-chron ordered list with author + timestamp + body + optional link).
  • client/src/pages/ProjectDetailsPage.tsxTabsList grid-cols-3grid-cols-4 (per audit 2026-04-22 finding). Fourth "Updates" TabsTrigger + TabsContent that mounts ProjectUpdatesTab.

453395d#41: Post Update modal (write surface)

  • client/src/lib/siwsUtils.ts — new post-update action → "Post an update to <project> on Stadium" (matches the server-side regex).
  • client/src/components/project/PostUpdateModal.tsx (new) — body textarea with live character counter (1..2000), optional link (mirrors server's validateSimpleUrl), inline errors per field, SIWS flow via web3Enable → web3Accounts → web3FromSource, posts via api.postProjectUpdate.
  • client/src/components/project/ProjectUpdatesTab.tsx — new props projectTitle, canPost, connectedAddress. Post button is rendered only when canPost === true AND a wallet is connected. New updates are prepended to local state without a page reload.
  • client/src/pages/ProjectDetailsPage.tsx — passes canPost = (isTeamMember || isAdmin) && Boolean(connectedAddress) and the wallet address through.

Validation strategy (per spec §10)

  • Body length 1..2000 enforced on both client and server.
  • Link URL prefix rules (www, http://, https://) identical on client and server.
  • No new client-only regexes; no revalidation divergence.

Test plan

Automated:

  • cd server && npm test → 22/22 (9 new + 13 existing).
  • cd client && npm run build → clean (pre-existing chunk-size + dynamic-import warnings unrelated).

Playwright (Block B gate, run against the Vercel preview once CI builds it):

  • On /m2-program/<any-project-id>, the tab strip shows exactly four tabs: Overview / Milestones / Team & Payments / Updates.
  • On /m2-program/plata-mia-15ac43, clicking Updates shows two seeded updates in reverse chronological order with body, timestamp, author, and at least one link.
  • On /m2-program/<a-project-with-no-updates>, Updates tab shows the warm empty-state copy.
  • On /m2-program/<any-project-id>, an un-authenticated visitor does NOT see a "Post update" button.
  • The Updates tab's post-button is in the DOM only when the wallet is connected AND a team member (check in mock mode by stubbing the wallet state).

SIWS-gated (manual QA with a real wallet; Playwright can't sign):

  • Connect a wallet that is a team member on the target project → Post update button appears.
  • Open modal, type a short body, submit → SIWS prompt, sign, modal closes, new update appears at top of list without reload.
  • Body > 2000 chars → inline error before submit, no network request.
  • Invalid link URL (e.g. not-a-url) → inline error before submit.
  • Wallet not on team → button hidden even after connect.
  • Admin wallet on a project they're not a member of → button visible (admins can post).

Out of scope (deferred per spec)

  • Editing or deleting updates (intentional immutability; spec §4.3).
  • Notifications when updates are posted (Phase 2+).
  • Cross-project updates feed (Phase 2+).

Ops

No deploy-time seed needed. The project_updates table is created by the migration; existing projects simply have no rows and the UI shows its empty state.

sacha-l added 3 commits April 22, 2026 23:21
Schema and API for project updates. Teams can post a short timestamped
text entry (body ≤ 2000 chars, optional link) against a project. No UI
in this commit — that's #40 (read) and #41 (write modal).

Phase 1 revamp Block B, issue #39. See docs/stadium-revamp-phase-1-spec.md §4.3.

Server:
- supabase/migrations/20260422010000_create_project_updates.sql —
  CREATE TABLE with CHECK (length(body) BETWEEN 1 AND 2000), CASCADE
  on project_id delete, index on (project_id, created_at DESC).
- server/api/repositories/project-update.repository.js — listByProject
  (reverse-chron, default limit 100) + create.
- server/api/services/project-update.service.js — thin wrap.
- server/api/controllers/project.controller.js — two new handlers:
  getProjectUpdates (public, 404 on unknown project) and
  postProjectUpdate (trims inputs, 422 on validation failure, 201 on
  success).
- server/api/routes/m2-program.routes.js — GET and POST
  /:projectId/updates; POST gated by requireTeamMemberOrAdmin.
- server/api/utils/validation.js — validateProjectUpdate (body 1..2000,
  optional linkUrl validated with the existing validateSimpleUrl
  helper).
- server/api/middleware/auth.middleware.js — adds "Post an update on
  Stadium" to VALID_STATEMENTS and /^Post an update to .+ on Stadium$/
  to projectPatterns.

Immutable write model — no PATCH, no DELETE on updates. Typo
corrections happen by posting a new update.

Tests:
- server/api/controllers/__tests__/project-update.test.js — 9 tests
  covering: 404 on missing project (GET), empty list, populated list,
  404 on missing project (POST), body empty/too-long/malformed-URL
  422s, happy-path 201 with field trimming, linkUrl trim pass-through.
- vitest: 22/22 (9 new + 13 existing).
Adds a new "Updates" tab alongside Overview / Milestones / Team &
Payments. Read-only — posting lands in #41.

Phase 1 revamp Block B, issue #40.

Client:
- client/src/lib/api.ts — ApiProjectUpdate type + api.getProjectUpdates
  (mock-mode reads mockProjectUpdates.ts, sorted reverse-chronological)
  + api.postProjectUpdate (used by #41; staged here so the next commit
  only adds UI).
- client/src/lib/mockProjectUpdates.ts (new) — three seeded updates
  across Plata Mia (2) and Kleo Protocol (1) so preview mode shows
  both the empty state (most projects) and the populated state.
- client/src/components/project/ProjectUpdatesTab.tsx (new) — handles
  loading, error, empty (warm copy per aesthetic guidance), and
  populated states. Updates rendered as an ordered list with author
  (truncated wallet address), timestamp, body (whitespace preserved),
  and optional link.
- client/src/pages/ProjectDetailsPage.tsx — TabsList bumped from
  grid-cols-3 to grid-cols-4; new "Updates" TabsTrigger; new
  TabsContent that mounts ProjectUpdatesTab.

Verification:
- npm run build: clean (pre-existing chunk-size + dynamic-import
  warnings unrelated).
- Spec compliance: tab rendered in the fourth position; empty state
  copy reads in builder voice; real link anchors open in new tab.

Out of scope (Issue #41):
- "Post update" button (team-gated) and modal.
Team members (or admins) on a project see a "Post update" button on
the Updates tab. Clicking it opens a modal with inline-validated body
and optional link fields; submit triggers a SIWS signature and POSTs
to /api/m2-program/:id/updates.

Phase 1 revamp Block B, issue #41. Closes Block B.

Client:
- client/src/lib/siwsUtils.ts — new 'post-update' action returning
  "Post an update to <project> on Stadium" (matches the server-side
  regex added in #39's commit).
- client/src/components/project/PostUpdateModal.tsx (new) — body
  textarea with live character counter (1..2000), optional link
  field (mirrors server's validateSimpleUrl: www / http / https only),
  inline error messages under each field, SIWS signing flow via
  web3Enable → web3Accounts → web3FromSource, uses api.postProjectUpdate.
- client/src/components/project/ProjectUpdatesTab.tsx — accepts new
  props (projectTitle, canPost, connectedAddress); renders the
  "Post update" button above the list (or empty state) only when
  canPost === true AND connectedAddress is set. New updates are
  prepended to local state without a page reload.
- client/src/pages/ProjectDetailsPage.tsx — passes canPost =
  (isTeamMember || isAdmin) && Boolean(connectedAddress) and the
  connectedAddress down to ProjectUpdatesTab.

Validation strategy (per spec §10):
- Body length bounds match the server (1..2000). Client enforces
  inline; server enforces authoritatively.
- Link URL prefix rules mirror server's validateSimpleUrl.
- No new client-only regexes; the shape of each validator is the same
  on both sides.

Verification:
- server/npm test: 22/22 passing (unchanged from #39).
- client/npm run build: clean.

Block B journey slice now testable:
  team member connects wallet → opens Updates tab → sees "Post update"
  → fills body → signs SIWS → update appears at top of list.
Fully verifiable via Playwright only up to the button-visibility
check (SIWS-gated writes require the manual lane per spec §12).
@sacha-l sacha-l added enhancement New feature or request revamp-phase-1 Stadium Phase 1 revamp — programs, updates, funding signals, applications labels Apr 22, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stadium Ready Ready Preview, Comment Apr 22, 2026 9:26pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request revamp-phase-1 Stadium Phase 1 revamp — programs, updates, funding signals, applications

Projects

None yet

Development

Successfully merging this pull request may close these issues.

revamp-P1-04: project_updates data model + server API

1 participant