feat(revamp): Block B — project updates (closes #39 #40 #41)#53
Draft
feat(revamp): Block B — project updates (closes #39 #40 #41)#53
Conversation
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).
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 APIsupabase/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.project.controller.jswithgetProjectUpdates(public, 404 on missing project) andpostProjectUpdate(422 on validation failure, trims body/linkUrl before validation, 201 on success).m2-program.routes.js—GET /:projectId/updates(public) andPOST /:projectId/updates(requireTeamMemberOrAdmin).validation.js—validateProjectUpdate(body 1..2000, optional linkUrl via existing validateSimpleUrl).auth.middleware.js— adds the/^Post an update to .+ on Stadium$/pattern + a base statement.fa3af45— #40: Updates tab (read surface)client/src/lib/api.ts—ApiProjectUpdatetype +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.tsx— TabsListgrid-cols-3→grid-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— newpost-updateaction →"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 viaweb3Enable → web3Accounts → web3FromSource, posts viaapi.postProjectUpdate.client/src/components/project/ProjectUpdatesTab.tsx— new propsprojectTitle,canPost,connectedAddress. Post button is rendered only whencanPost === trueAND a wallet is connected. New updates are prepended to local state without a page reload.client/src/pages/ProjectDetailsPage.tsx— passescanPost = (isTeamMember || isAdmin) && Boolean(connectedAddress)and the wallet address through.Validation strategy (per spec §10)
www,http://,https://) identical on client and server.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):
/m2-program/<any-project-id>, the tab strip shows exactly four tabs: Overview / Milestones / Team & Payments / Updates./m2-program/plata-mia-15ac43, clicking Updates shows two seeded updates in reverse chronological order with body, timestamp, author, and at least one link./m2-program/<a-project-with-no-updates>, Updates tab shows the warm empty-state copy./m2-program/<any-project-id>, an un-authenticated visitor does NOT see a "Post update" button.SIWS-gated (manual QA with a real wallet; Playwright can't sign):
not-a-url) → inline error before submit.Out of scope (deferred per spec)
Ops
No deploy-time seed needed. The
project_updatestable is created by the migration; existing projects simply have no rows and the UI shows its empty state.