Student Projects Gallery (read + submit)#20
Conversation
…lt-with-this)
Add a Notion-backed Projects database layer and the read-side UI for the
Student Projects Gallery:
- notion.ts: optional NOTION_DB_PROJECTS env (independent of the strict
catalog contract), ProjectRecord + pageToProject parser, fetchAllProjects
(published-only option), fetchProject, and createProject (writes
published=false, photo file_upload attachments, tools_used relation ids).
- projects.ts: cached getPublishedProjects/getProject behind "use cache" +
cacheTag("projects") + cacheLife("minutes"), plus getProjectsForTool for
the "Built with this" tool-detail section.
- /projects gallery grid of published projects + "Submit a project" CTA;
/projects/[id] detail with markdown body, photo gallery, tool chips,
materials, optional link.
- DetailShell: "Built with this" section (hidden when none).
- i18n: new keys across all 12 message catalogs (en authoritative; others
machine-translated, pending native QC).
- Empty-env safe: no NOTION_DB_PROJECTS renders empty states without crashing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- POST /api/projects: validates title/author/body, optional photos/tools/
link/materials, rate-limited via rate-limit.ts (projects:${ip}, 10/min),
calls createProject() with published=false, returns the new page id. Node
default runtime (no runtime export). 503s gracefully when projects env is
unset.
- /projects/new: client submission form (title, author, markdown body with
live preview, photo upload reusing /api/upload-notion, tool multiselect
from getCatalogTools, optional link + materials) with confirmation and
inline error states.
- /api/admin/revalidate: also busts the "projects" cache tag so newly
published projects appear without waiting for cacheLife.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3835b2b6c2
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| published: { checkbox: false }, | ||
| }; | ||
| if (fields.author) properties.author = richTextProp(fields.author); | ||
| if (fields.body) properties.body = richTextProp(fields.body); |
There was a problem hiding this comment.
Split long write-ups before writing to Notion
When a student enters a write-up longer than 2,000 characters, the API accepts it up to MAX_BODY_CHARS = 20000 but this line serializes the entire body as one Notion rich_text text object. Notion rejects individual rich text content blocks over 2,000 characters, so otherwise-valid longer project submissions fail with the generic 502 instead of being saved; chunk the body into multiple rich text objects or lower the accepted limit.
Useful? React with 👍 / 👎.
| }; | ||
| } | ||
| if (fields.photo_uploads?.length) { | ||
| properties.photos = fileUploadsProp(fields.photo_uploads); |
There was a problem hiding this comment.
Use the file-upload Notion version for photo submissions
When a project includes photos, this attaches file_upload objects but the create-page request goes through projectsRequest, which still sends the shared 2022-06-28 Notion version; the existing upload route uses 2026-03-11 specifically because file uploads require the newer version. That means submissions with uploaded photos can complete the upload step and then fail during project creation, breaking the advertised photo submission flow.
Useful? React with 👍 / 👎.
Verified the projects pages by rendering them locally with Playwright in dark mode (gallery, detail, form, built-with-this) and fixed the contrast bugs the token sweep missed — all caused by controls/markdown using page theme tokens while sitting on the light .tool-detail "spec sheet" panel (or vice versa): - /projects gallery: tool chips on cards rendered white-on-white in dark mode (.td-chip's hardcoded light defaults). Re-mapped .project-card-tools chips to the page theme tokens. - /projects gallery: "Submit a project" / empty-state buttons were invisible (transparent bg, white text) because .td-button reads --td-* tokens that only exist inside .tool-detail. Supplied those tokens locally (accent = --primary). - /projects/[id] markdown body: headings, bold, inline code, <pre>, table headers and blockquotes rendered black-on-black / white-on-white on the light panel. Scoped .chat-markdown to the .tool-detail (--td-*) tokens. - /projects/new form: inputs, textarea, live-preview panel and tool-pick chips rendered as dark boxes on the white panel. Re-mapped form controls + preview markdown to the light --td-* tokens; placeholders and error text too. Light mode unchanged (the --td-* tokens are the same light values in both themes; gallery chips/buttons use page tokens that already resolved correctly in light). typecheck + lint + build all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds the Student Projects Gallery to v5 — a gallery of student project write-ups (blog-style markdown) backed by a new Notion Projects database, with bidirectional linking between projects and the tools used to build them. Students submit via a form (no AI); submissions land unpublished and a staff member flips
publishedin Notion to make them public.Built per the design spec (committed at
docs/superpowers/specs/2026-05-29-gallery-projects-design.md). One PR, two logical commits: read side, then submit side./projectsgallery grid of published projects + a visible "Submit a project" CTA;/projects/[id]detail (react-markdown + remark-gfm body, photo gallery, tool chips →/tools/[id], materials, optional link); a "Built with this" section on the tool detail listing published projects that reference the tool (hidden when none)./projects/newform (title, author, markdown body + live preview, photo upload reusing/api/upload-notion, tool multiselect, optional link + materials) →POST /api/projects(validates, rate-limitsprojects:${ip}at 10/min, callscreateProject()withpublished=false, returns the new id).projectstag ("use cache"+cacheTag+cacheLife("minutes"));POST /api/admin/revalidatenow busts bothcatalogandprojects.NOTION_DB_PROJECTS,/projectsrenders an empty state (no crash) and/projects/newstill loads. Verified by a fullnext buildin the sandbox (env unset).Notion schema — new
ProjectsdatabaseThe operator must create this DB in Notion, share it with the integration, and set
NOTION_DB_PROJECTS.titleauthorbodyphotostools_usedlinkmaterialspublisheddateProperty parsing tolerates both lower and Title-cased names (matches existing parsers).
Routes
GET /projects— published gallery grid + submit CTAGET /projects/[id]— project detail (markdown, photos, tool chips, materials, link)GET /projects/new— submission formPOST /api/projects— create submission (published=false), rate-limited, returns{ id }POST /api/admin/revalidate— now bustscatalog+projects/tools/[id]— gains a "Built with this" sectionOperator follow-up (before the feature works in prod)
ProjectsNotion DB with the schema above; add thetools_usedrelation to the Tools DB.NOTION_DB_PROJECTSin Vercel (all environments).projects(andcatalog) cache tags viaPOST /api/admin/revalidate.publishedin Notion, confirm it appears in/projects, its detail renders, and the linked tools show it under "Built with this".i18n
All new UI strings (gallery, detail, form, confirmation/errors) added to all 12 message catalogs. English is authoritative; the other 11 are machine-translated and need native QC. Project content is never translated. Key parity verified across all locales.
Test plan
npm run typecheck,npm run lint, andnpm run buildpass (full build, env unset).NOTION_DB_PROJECTS,/projectsrenders an empty state (no crash) and/projects/newstill loads (only needs the Tools catalog + submit endpoint, which 503s gracefully).published=false, photos attached, tool relations set. (needs the DB)/projects,/projects/[id], or "Built with this" — enforced by published-only filtering and resolving detail from the cached published set. (needs the DB)published=true+ cache bust, the project appears, detail renders markdown + photos, tool chips link, and tool pages show it under "Built with this". (needs the DB)🤖 Generated with Claude Code