feat(public-screens): all read-only screens wired to read API#28
Merged
Conversation
npm install -w apps/web @tanstack/react-query
API layer wraps the response envelope from specs/api/conventions.md into typed Promise calls. ApiError carries status/code/fields/traceId so screens can branch on 404 vs validation vs server. The TanStack Query client wires queryCache.onError to NetworkErrorBanner.showError() for 5xx and network failures.
MarkdownView sets dangerouslySetInnerHTML on the server-rendered sanitized HTML (overviewHtml, bioHtml, descriptionHtml) per specs/behaviors/markdown-rendering.md — no client-side markdown lib in the bundle. StageBadge / StageProgressBar carry the seven-stage palette and descriptions from specs/behaviors/project-stages.md. TagChip color-codes by namespace per specs/behaviors/tags.md. FacetSidebar drives Topics/Tech/Events/Stages from metadata.facets. ActivityCard discriminates update vs buzz cards per specs/behaviors/activity-feed.md and exports mergeActivity() for the home + project-detail mixed feeds.
Replaces ComingSoon placeholders in App.tsx with: - Home: hero, featured projects grid, get-involved tiles, activity stream with All/Updates/Buzz filter chips (client-side merge), help-wanted rail - ProjectsIndex: URL-driven filters (q, tag*, stage*, sort, page, helpWanted, includeDeleted-for-staff), facet sidebar (Topics/Tech/Events/ Stages tabs), debounced search, sortable, paginated - ProjectDetail: overview + open help-wanted + merged activity feed + members sidebar + tags + share + info; respects /projects/:slug/updates/ :number and /projects/:slug/buzz/:buzzSlug anchor variants; action buttons gated by response.permissions, with "Sign in to ..." replacements for anonymous callers - PeopleIndex: grid with Topics/Tech facet sidebar, q+sort+page URL state - PersonDetail: profile, projects sorted by maintainer/joinedAt, recent authored updates - HelpWantedIndex: Tech/Topics facets + commitmentMax radio filter - ProjectUpdatesFeed, ProjectBuzzFeed: global reverse-chron feeds - TagsOverview, TagsNamespace, TagDetail: full taxonomy browse - Volunteer, Sponsor: static content + live project count / featured roles - /people redirects to /members; /search redirects to /projects?q=… (the dedicated search page is deferred per behaviors/app-shell.md) URL state is the source of truth via useSearchParams so the queryKey re-fetches and back/forward + share-links work cleanly.
Replaces the mockSearch stub from web-shell with three parallel calls to /api/projects, /api/people, /api/tags (perPage=4 each) per specs/behaviors/app-shell.md. Results render in grouped sections (Projects / Members / Tags). 5xx and network failures surface via useNetworkError().showError(). Enter routes to /projects?q=… per the deferred-/search behavior in app-shell.md.
renderScreen wires a fresh QueryClient + NetworkErrorProvider + TooltipProvider + MemoryRouter around the screen under test, plus mockOk/mockPaginated helpers matching the response envelope. Smoke tests cover the Display Rules in each screen spec: - Home: hero headline + volunteer CTA + get-involved cards - ProjectsIndex: header with totalItems badge, project card with stage + help-wanted badge, Add Project hidden for anonymous - ProjectDetail: title, server-rendered overview HTML, sign-in CTA for anonymous, project-info sidebar with users-site + chat-channel links - HelpWantedIndex: role with commitment chip + sign-in CTA, all four commitment radio options - useSearch: confirms the three parallel calls to /api/projects, /api/people, /api/tags with perPage=4 (replaces mockSearch stub validation in web-shell) AppHeader test wraps in NetworkErrorProvider now that SearchBox depends on useNetworkError().
The session endpoint returns { person: …|null, accountLevel: … } per
specs/api/auth.md — not a bare AuthPerson — so fetchMe() reaches into
data.person. Without this the header crashes on first render with
"Cannot read properties of undefined (reading 'charAt')" when accountLevel
is present but no person is attached.
This was referenced May 16, 2026
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.
Summary
<ComingSoon />placeholder from web-shell with a real screen wired to the read API: Home, ProjectsIndex, ProjectDetail (+ permalink variants for updates and buzz), PeopleIndex, PersonDetail, HelpWantedIndex, ProjectUpdatesFeed, ProjectBuzzFeed, TagsOverview, TagsNamespace, TagDetail, Volunteer, Sponsor./people→/members;/search?q=→/projects?q=(the dedicated search page is deferred perbehaviors/app-shell.md).apps/web/src/lib/api.tsfetcher around the response envelope fromspecs/api/conventions.md. TanStack Query is the cache + loading layer;queryCache.onErrorwires 5xx and network failures touseNetworkError().showError()(closes one of the two web-shell absorptions). URL query params are the source of truth for filter / sort / page / search state on every index screen, so back / forward / share-links work cleanly.mockSearchinuseSearch.tswith three parallel calls to/api/projects,/api/people,/api/tags(perPage=4 each) perbehaviors/app-shell.md; SearchBox renders results grouped by type. Enter submits to/projects?q=…(closes the other web-shell absorption).<ProjectCard>,<PersonCard>,<HelpWantedCard>,<StageBadge>+<StageProgressBar>(palette + descriptions frombehaviors/project-stages.md),<TagChip>(namespace color-coding),<FacetSidebar>(Topics / Tech / Events / Stages tabs),<ActivityCard>(update vs buzz discriminator +mergeActivity()perbehaviors/activity-feed.md),<MarkdownView>(setsdangerouslySetInnerHTMLon the server-rendered sanitized HTML — no client-side markdown lib in the bundle),<PersonAvatar>,<Pagination>.ProjectDetailreadspermissionsfrom the API response to gate UI: anonymous gets a "Sign in to contribute" CTA and "Sign in to express interest" replacements on help-wanted role cards (the actual auth-gated mutations land inauthoring-screens).useAuthnow parses the{ person, accountLevel }envelope shape the auth-jwt-substrate endpoint actually returns; the old code crashed the header on first paint as soon as the API was reachable.Test plan
npm run lint— greennpm run type-check— greennpm run build— green; bundle contains noremark/marked/markdown-it/micromark(grepondist/assets/*.jsreturns nothing)npm run -w apps/web test— 27 / 27 pass; new smoke tests render each major screen against fixture API responses and verify documented Display Rulesnpm run -w packages/shared test— 52 / 52 pass/api/projects?q=,/api/people?q=,/api/tags?q=all answer 200 with the expected envelope so SearchBox typeahead is wired throughapps/api/changes; the suite ran in PR feat(read-api): GET endpoints for projects, people, tags + FTS + serializers #22)🤖 Generated with Claude Code