Skip to content

Student Projects Gallery (read + submit)#20

Draft
philosophercode wants to merge 3 commits into
mainfrom
philosophercode/projects-gallery
Draft

Student Projects Gallery (read + submit)#20
philosophercode wants to merge 3 commits into
mainfrom
philosophercode/projects-gallery

Conversation

@philosophercode

Copy link
Copy Markdown
Owner

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 published in 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.

  • Read side: /projects gallery 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).
  • Submit side: /projects/new form (title, author, markdown body + live preview, photo upload reusing /api/upload-notion, tool multiselect, optional link + materials) → POST /api/projects (validates, rate-limits projects:${ip} at 10/min, calls createProject() with published=false, returns the new id).
  • Cache: new projects tag ("use cache" + cacheTag + cacheLife("minutes")); POST /api/admin/revalidate now busts both catalog and projects.
  • Empty-env safe: with no NOTION_DB_PROJECTS, /projects renders an empty state (no crash) and /projects/new still loads. Verified by a full next build in the sandbox (env unset).

Notion schema — new Projects database

The operator must create this DB in Notion, share it with the integration, and set NOTION_DB_PROJECTS.

Field Type Notes
title title Project name
author rich_text Student name/handle (free text; no account)
body rich_text Markdown write-up
photos files Uploaded images (first = cover)
tools_used relation → Tools DB Bidirectional — auto-creates a back-relation on Tools
link url Optional
materials multi_select Optional
published checkbox Default false. Staff gate.
date created_time Submission timestamp

Property parsing tolerates both lower and Title-cased names (matches existing parsers).

Routes

  • GET /projects — published gallery grid + submit CTA
  • GET /projects/[id] — project detail (markdown, photos, tool chips, materials, link)
  • GET /projects/new — submission form
  • POST /api/projects — create submission (published=false), rate-limited, returns { id }
  • POST /api/admin/revalidate — now busts catalog + projects
  • /tools/[id] — gains a "Built with this" section

Operator follow-up (before the feature works in prod)

  1. Create the Projects Notion DB with the schema above; add the tools_used relation to the Tools DB.
  2. Share the DB with the integration; set NOTION_DB_PROJECTS in Vercel (all environments).
  3. Redeploy / purge the projects (and catalog) cache tags via POST /api/admin/revalidate.
  4. Submit a test project, flip published in 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, and npm run build pass (full build, env unset).
  • With no NOTION_DB_PROJECTS, /projects renders an empty state (no crash) and /projects/new still loads (only needs the Tools catalog + submit endpoint, which 503s gracefully).
  • Submitting the form creates a Notion page with published=false, photos attached, tool relations set. (needs the DB)
  • An unpublished project does NOT appear in /projects, /projects/[id], or "Built with this" — enforced by published-only filtering and resolving detail from the cached published set. (needs the DB)
  • After flipping 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)
  • Submit endpoint returns 429 past the rate limit (10/min via existing limiter).

🤖 Generated with Claude Code

philosophercode and others added 2 commits May 29, 2026 02:38
…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>
Copilot AI review requested due to automatic review settings May 29, 2026 06:39
@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
makerlab-tools Error Error May 29, 2026 7:16am
makerlab-tools-g4gb Ready Ready Preview, Comment May 29, 2026 7:16am

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread v5/src/lib/notion.ts
published: { checkbox: false },
};
if (fields.author) properties.author = richTextProp(fields.author);
if (fields.body) properties.body = richTextProp(fields.body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread v5/src/lib/notion.ts
};
}
if (fields.photo_uploads?.length) {
properties.photos = fileUploadsProp(fields.photo_uploads);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants