Skip to content

Relaunch: AI-builders repositioning — new shell & feed, URL/SEO restructure, moderation pipeline, achievements, hardening#1331

Merged
NiallJoeMaher merged 188 commits into
codu-code:developfrom
NiallJoeMaher:feat/relaunch-repositioning
Jun 13, 2026
Merged

Relaunch: AI-builders repositioning — new shell & feed, URL/SEO restructure, moderation pipeline, achievements, hardening#1331
NiallJoeMaher merged 188 commits into
codu-code:developfrom
NiallJoeMaher:feat/relaunch-repositioning

Conversation

@NiallJoeMaher

@NiallJoeMaher NiallJoeMaher commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

The full Codú relaunch: repositioned as the community for AI builders & indie hackers, with a new product surface, a new content-URL scheme built for SEO/AEO, a moderation-gated publishing pipeline, achievements, and a long hardening pass. 181 commits, migrations 00110037 (run automatically by vercel-build on production).

Product

App shell & design system

  • Design-token system (status colors/washes, radii/shadow/motion scale, WCAG-AA text tones); 3-column shell with sticky top bar, left rail + "Your topics", right rail, ⌘K command palette, mobile bottom nav.
  • Feed is the homepage (/): editorial rows with kind chips, vote/save/share actions, Type · Sort · Topic filters; /discussions index. Post kinds til and question added.
  • Single “+ Create” entry → dos-&-don'ts gate → compose modal (discussion/link) or the restyled article editor. User-facing scheduling removed (moderators schedule via the admin queue).
  • Onboarding: pick topics → follow 3 builders → leave a first comment, tracked server-side, rewarded with the new First Steps badge.

Achievements

  • Badge system (badge/user_badge) with 7 badges; every newly earned badge fires an app-wide confetti unlock dialog exactly once across devices (user_badge.celebrated_at, backfilled so nothing retro-celebrates; existing qualifying members are pre-granted the onboarding badge silently — migration 0037).
  • Points + streaks power the rest (point_event, user_streak); discussion.create now awards comment points (it previously awarded nothing).

Moderation pipeline (env-gated: MODERATION_ENABLED)

  • Publish paths route through a dedupe + heuristic screen + Bedrock auto-review gate; admin queue with approve / approve-&-schedule / reject / hide; scheduled releases promoted by an EventBridge lambda → /api/cron/promote-scheduled (secret-authenticated, idempotent go-live side effects: points, notification, IndexNow).
  • Authors cannot self-schedule, backdate publishedAt, or force-publish around a moderator's schedule/rejection on any mutation path (publish and update are guarded; the legacy /alpha editor and its unguarded post.* write mutations are deleted outright).

URLs, SEO & AEO

  • New canonical scheme: /{username}/{slug-urlId} for member content, /d/{slug} discussions, /s/{source}/{slug} aggregated content, /tag/{slug} landing pages (paginated). Slugs end with an immutable urlId, so title edits and renames 301 forever; legacy URLs (/articles/*, /feed/*, sources, /courses, /alpha) all 301.
  • Usernames are case-insensitive GitHub-style: display casing preserved, lowercase reserved (lower(username) unique index), every resolver 301s to stored casing.
  • Crawler-ready HTML: the feed, discussions, /s/ pages and link posts server-render their first page/content (AI crawlers don't run JS); JSON-LD (Article, NewsArticle, DiscussionForumPosting incl. comments, ProfilePage, Organization, WebSite, breadcrumbs); rebuilt sitemap (content, tags, sources, non-thin profiles); IndexNow pings on every go-live path; llms.txt; robots policy with explicit AI-crawler allowances; RSS rebuilt on the new tables/URLs.
  • Fixed along the way: /og images 500ed for all Twitter cards (and were robots-blocked), magic-link sign-in was silently broken (next-auth v5 provider-id mismatch), session callback never set session.user.username.

Hardening & performance

  • Rate limiting: DynamoDB-backed limiter as tRPC middleware across publish/comment/vote/report/profile/job/follow/tag mutations (RATE_LIMIT_TABLE).
  • SSRF guard for OG fetching, JSON-LD escaping, reserved-username namespace, httpUrl() zod hardening, report-email throttling, urlId-collision hijack guard.
  • Perf: React cache() dedupes metadata+page resolution (content pages ~halved their queries), parallelized layout/engagement queries, feed stops shipping full post bodies, lower(username)/urlId/feed indexes.
  • Dependencies: npm audit 53 vulnerabilities (1 critical / 13 high) → 8 (0 high/critical). next → 16.2.9, drizzle-orm → 0.45.2 (SQLi advisory), drizzle-kit → 0.31.10, sentry + transitive bumps. Residual: nodemailer 7.x moderate (next-auth peer-pins ^7; vulnerable param never user-controlled here) and Next's internally-pinned postcss.

Testing & DX

  • 131 Playwright e2e tests green (routing/redirect regression suite, moderation, discussions, saved, notifications, email); 82 unit tests; tsc/eslint clean; prod build verified.
  • Mailpit local email catcher in docker-compose (EMAIL_PROVIDER=local, UI at localhost:8027) — dev and e2e exercise real email delivery without SES; hard-disabled on production builds.

Deploy checklist

  • Prod env vars: MODERATION_ENABLED=true, RATE_LIMIT_TABLE, CRON_SECRET (must match the per-account /env/cronSecret SSM param), EMAIL_AUTH_ENABLED as desired.
  • SSM params per account for the cron lambda: /env/siteUrl, /env/cronSecret (lambda fails closed without them) — see cdk/README.md.
  • Deploy CronStack (scheduled-post promotion lambda + EventBridge rule).
  • Migrations run automatically on the production Vercel build; the shared dev DB must be migrated manually for previews (npm run db:migrate).

🤖 Generated with Claude Code

NiallJoeMaher and others added 30 commits June 6, 2026 22:00
Shift positioning from 'the free web developer community' / 'Ireland's largest
web developer community' to 'Codú — the community for AI builders & indie
hackers' across the homepage, app layouts, hero, newsletter CTA, structured data
(website + organization JSON-LD), manifest, RSS feed, the advertise page, and the
profile/volunteer title metadata.

Also refocus SEO keywords away from generic web-dev/React terms toward the
AI-builder / indie-hacker stack (LLM apps, agents, RAG, prompt engineering, vibe
coding, SaaS, MVP, bootstrapping), and reconcile the advertise metrics by dropping
the contradictory '100,000+' and the 'Ireland' geography claim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New server-rendered About page reusing the Sponsorship visual system: hero with
the positioning line, a 'What Codú is for' manifesto, a 'What you get' card grid
(tutorials, feed, newsletter, Discord), a small honest founder note (not a flex),
and a closing CTA. Add /about to the sitemap's indexed routes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build the job board from the existing create-form stub:
- job table + job_type/job_status enums + migration 0020 (AI-native tagging,
  featured/paid tier, full lifecycle, payment columns for a future provider)
- tRPC jobRouter: create (+ admin email), public list (remote/AI-native/type
  filters, featured-first, keyset pagination) + getBySlug, owner getById/myJobs,
  admin adminList/moderate/setFeatured, and a markPaid payment stub
- wire the create form to the mutation; add AI-native toggle + tags input
- /jobs listing + /jobs/[slug] detail pages
- flag-gated Jobs nav link in the sidebar (+ the /about nav link)

Positioned for AI developer roles (employment), not indie hackers (self-employed
builders aren't job-seeking). Jobs UI is gated behind FEATURE_FLAGS.JOBS
(auto-on in dev, dark in prod until the PostHog flag is enabled).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expand the content aggregator toward the AI-builder positioning: add 26
RSS-verified AI sources (Latent Space, Simon Willison, Ahead of AI, Import AI,
Eugene Yan, Lilian Weng, Chip Huyen, Hamel Husain, Jay Alammar, Interconnects,
One Useful Thing, AI Snake Oil, The Gradient, Google DeepMind, Microsoft AI,
AWS ML, NVIDIA, Together AI, Replicate, Ollama, LlamaIndex, Roboflow, fast.ai,
Answer.AI, BAIR, Apple ML) to drizzle/seed-sources.ts.

Takes the 'ai' category from 6 to 32 sources. Additive only — existing web-dev
sources are kept (web-dev content stays, just no longer the headline). Seed is
idempotent (insert-by-url), so re-running only adds the new feeds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A dev-gated (NODE_ENV) design canvas at /kitchen-sink for iterating on the
redesign. Proposes a fresh, editorial direction — cool dark canvas, Bricolage
Grotesque display, Hanken Grotesk body, JetBrains Mono micro-labels — and drops
the orange/pink gradient entirely. Renders four fresh accent options (Mint, Lime,
Sky, Iris) side-by-side plus tokens, buttons, form controls, tags, a job card,
and an applied hero so we can lock a theme before touching real pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s living design system

- Load Bricolage Grotesque / Hanken Grotesk / JetBrains Mono via next/font in the
  root layout and expose them as --font-display / --font-sans / --font-mono.
- Rebuild /kitchen-sink as the canonical, dev-only LIVING design system: Mint
  accent locked, cool-dark tokens, organized Foundations / Components / Patterns to
  grow as we build real pages.
- Apply the anti-trope direction researched this session: editorial 'number +
  hairline + display-title' section headers instead of the common '// label'
  dev-tool convention, hairline rules over soft shadows, no bento/blobs.
- Include a side-by-side section-label comparison (code-comment vs slash vs
  editorial) to decide the structural device.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the prototype's inline styles with a real system:
- Add CSS-variable-driven design tokens (canvas/surface/elevated/hairline/fg/
  muted/faint/accent) in styles/globals.css (dark-first via .dark) and register
  them in tailwind.config.js with opacity support, plus font-display/sans/mono.
- Restyle global .primary-button / .secondary-button / .focus-style to the Mint
  accent — removes the orange→pink gradient app-wide.
- Add reusable components/ds: Eyebrow ('// label' mono, the approved convention)
  and Tag (default/accent/soft pills).
- Refactor /kitchen-sink to consume theme classes + ds components (no inline
  colors/fonts), wrapped in 'dark' so it always shows the canonical palette.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nnel)

Start the growth-driven page rebuild:
- New NewsletterCapture ds component (inline + compact) with real on-site beehiiv
  capture via a subscribeNewsletter server action (degrades gracefully in dev).
  Replaces the old blue->pink gradient NewsletterCTA on the homepage rail.
- Rebuild the Hero as a token-driven server component: editorial dotted-grid
  atmosphere (no gradient/starfield), Bricolage display H1 with the accent on
  'AI builders', mono eyebrow, single primary CTA (Join free) + Browse feed.
- Rework the post-hero band from a clashing light-gray duplicate CTA into a dark,
  on-system 'the community / build in public' beat with one CTA.

No vanity metrics (social proof deferred per owner). Funnel: cold -> subscribe,
warm -> join free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stop re-skinning the old app shell — give the front door its own layout.
- Force dark-only (next-themes forcedTheme + html.dark).
- New (marketing) route group: top-nav + footer shell (no app sidebar), full-bleed,
  with the org JSON-LD and a footer newsletter capture.
- Rebuild the homepage from scratch as an editorial landing: hero (accent glow +
  dotted grid), a real 'Fresh from the feed' proof-of-life grid (live AI posts),
  a numbered 'why Codú' value grid, and a community beat — all token-driven, no
  vanity metrics. Move it out of the (app) sidebar layout.
- The (app) sidebar shell stays for the logged-in app pages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make 'Fresh from the feed' read as clickable articles, not an info box:
- pull post cover images and render them as CSS background images (external feed
  domains aren't allowed by next/image), with an on-brand dotted fallback when a
  post has no cover
- switch from the seamless gap-px grid to separated cards with hover-lift,
  accent border on hover, and a 'Read ›' affordance

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…existing logo

- Move get-started into a new (auth) route group with a bare layout (no nav,
  no sidebar, no footer) for a zero-distraction sign-in/sign-up.
- Rebuild the auth UI on the new tokens: mono eyebrow, Bricolage heading, clean
  bordered GitHub/GitLab buttons (no orange/pink gradient), terms/privacy note.
- Use the existing Codú wordmark logo (/images/codu.png) in the auth header,
  marketing nav, and footer instead of a text wordmark (new logo is a later task).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move /about out of the app sidebar layout into (marketing), rebuilt token-driven
(editorial hero, manifesto, what-you-get cards, small founder note, CTA) — drop
the old orange/pink Sponsorship-style sections and retire components/About.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…letter card sitewide

- App sidebar: canvas bg + hairline border + mint active icons (was bg-black /
  neutral); footer + socials to tokens.
- Swap the old blue->pink NewsletterCTA for the token-driven NewsletterCapture on
  /feed and /articles, and delete the dead NewsletterCTA component.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- De-bootstrap the nav: animated accent underline on links, refined spacing.
- Capture competitive learnings from IndieHackers (right-rail conversion hub with
  Submit-a-Post/Advertise, the Build Board leaderboard, content-forward layout) in
  the growth doc as the next-build backlog.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ct form

- Move /advertise into (marketing), rebuilt token-driven: hero, 'ways to partner'
  offering cards (newsletter/jobs/events/content), real past-partner logos kept as
  legit proof, dropped the vanity metrics + generic testimonial.
- Recolor the 3-step sponsor ContactForm from orange/pink to mint tokens.
- Retire the old (app)/advertise route + unused Sponsorship section components
  (keep ContactForm).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Recolor every remaining orange/pink utility to the mint accent across ~55 app +
  component files (feed, jobs, admin, editor, comments, badges, search, 404, etc.).
  Archived /letters posts left untouched.
- Homepage feed cards: remove the jump-on-hover; keep a subtle image zoom + chevron
  slide. Same de-jump applied to /about cards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New ConversionHub ds component (Write a post / Post a job / Advertise) surfaced at
the top of the feed sidebar, and retoken the 'About the Feed' card. Gives content
pages a clear contribute/monetise hub like IH's right rail.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WS3 v1, built safely (engagement never breaks core actions):
- Schema + migration: append-only point_event (with dedupe/anti-gaming index) and
  per-user user_streak.
- server/lib/engagement: award() (never throws) + recordDailyActivity() (idempotent
  daily streak roll-forward).
- Award points on post publish + comment create; roll the daily streak in the app
  layout for signed-in users.
- engagement tRPC router: myStats (streak + points) and leaderboard (week/all).
- UI: StreakBadge (🔥) in the app header for signed-in users; BuildBoard weekly
  leaderboard in the feed rail — flag-gated (FEATURE_FLAGS.BUILD_BOARD) and hidden
  while empty to avoid a ghost-town board.
- Fix the header Create button contrast (black on mint).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y + engagement-plan doc

- Kill remaining grey: retokenize UnifiedContentCard, the inline /articles ArticleCard
  + skeleton, SavedItemCard, PopularTagsSidebar, legacy ArticlePreview, and the feed
  Filters dropdowns to the design tokens (surface/canvas, hairline, fg/muted/faint,
  accent hover).
- Simplify taxonomy: remove the redundant rail 'Sources' filter (it duplicated the
  Topics dropdown via source category); sources remain as card attribution. Relabel
  the dropdown to 'Topics'.
- Add codu-relaunch/04-community/engagement-plan.md (shipped / building / deferred /
  last).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- New HeroBackdrop: refined mint aurora glows, a deterministic twinkling
  constellation, and occasional shooting stars — restrained, reduced-motion aware
  (motion-safe only). Replaces the flat dotted-grid+blur hero backdrop.
- Add reusable keyframes/animations to the Tailwind theme (twinkle/aurora/drift/
  shoot/rise) and a staggered load reveal on the hero eyebrow/H1/sub/CTAs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…play)

- Schema + migration: badge + user_badge (unique per user/badge).
- engagement.ts: BADGE_RULES + checkBadges() (runs after award; first post, 7/30-day
  streak, 100/500 points; connector wired in the referral step) + getUserBadges().
- Seed 6 starter badges (drizzle/seed-badges.ts).
- Display badge pills on the profile header.
- Reference + AI design prompt: codu-relaunch/04-community/badges.md; auto-mod plan:
  ai-moderation-plan.md (deferred).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… aurora

- Add ogl + a themed GradientBlinds component (reactbits-derived) — animated mint
  gradient blinds with a cursor spotlight, reduced-motion aware (single static frame).
- Use it as the homepage hero backdrop with a readability scrim; retire HeroBackdrop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Schema + migration: user.referralCode (unique) + user.invitedBy; add 'referral'
  to the point_action enum.
- engagement.ts: ensureReferral() (lazy code-gen + cookie attribution → award the
  referrer 'referral' points + checkBadges → Connector badge); wire real referral
  count into the connector badge rule.
- /get-started captures ?ref into a cookie; the app layout attributes it on next
  visit (idempotent, never throws).
- engagement.myReferral query; ReferralCard (invite link + count) in Settings →
  'Invite friends'.
- Backfill referral codes for existing users (drizzle/seed-referral-codes.ts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…first, composer

- Schema + migration: follow table (follower/following, unique pair, indexes).
- follow tRPC router: follow / unfollow / isFollowing / counts.
- Feed: 'following' filter in content.getFeed (+ fix the previously-unused tag
  filter) and a For-you / Following tab on /feed (signed-in), with an empty state.
- Profiles: FollowButton (Follow/Following, optimistic invalidation) + follower/
  following counts in the header.
- Feed-first: signed-in users are redirected from / to /feed (marketing stays for
  logged-out + SEO).
- Inline composer ('What are you building?') at the top of the feed.
- Datastore note: recommend staying on Postgres over a Mongo migration
  (codu-relaunch/01-platform/datastore-consideration.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…llowing lists

- Follow notifications: NEW_FOLLOWER type, created on a new follow; widen the
  notification read filters (type 0/1/2, drop the postId-not-null requirement) and
  render 'started following you' in the notifications list.
- Achievements section on profiles (engagement.profileEngagement): current/longest
  streak, points, and the full badge set with earned + LOCKED states + criteria
  (the 'potential awards' view). Replaces the simple header pills.
- Followers / Following lists: follow.getFollowers/getFollowing + clickable counts
  that reveal the list (with avatar, name, Follow button per row).

Verified logged-in: Follow → Following toggle, live count, followers list, and the
achievements panel all work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0 of the design-handoff recreation. Treat the handoff at
design_handoff_codu/ as the source of truth.

- styles/globals.css: full token set (surface ladder hover/inset,
  border-strong, on-accent, status colors success/warning/danger/info),
  fix dark --color-faint #5b6472 -> #757e8c for WCAG AA, align fg/accent-soft
  to handoff hexes, add focus-ring/shadow ladder CSS vars
- globals base layer: global mint :focus-visible ring, display-font headings,
  .eyebrow/.slash + .card utilities, reconcile primary/secondary/ghost buttons
  to handoff (on-accent text, radius-md, press translate), drop dead .old-input
- tailwind.config.js: expose new colors + washes (opacity modifiers), radii
  scale, boxShadow, container maxWidth, fontSize/letterSpacing scale, motion
  tokens, bg-grid-lines
- components/ds: Tag/FollowButton text-black -> text-on-accent
- kitchen-sink: full color ladder + status + elevation/radii + toggle refs

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1. Replace the single-sidebar layout with the handoff shell.

- components/Layout/AppShell: sticky TopBar over the .app-main 3-column grid
  (LeftRail / center / RightRail) + ⌘K palette + logged-out SignInBar; global
  ⌘K/Esc keydown handling
- TopBar: logo→/feed, search-button styled like an input (opens palette),
  Feed/Discussions/Jobs nav, Write + avatar menu / Log in + Join free
- LeftRail: primary nav (+ Notifications/Saved/Profile when authed), Your
  topics tags, bottom-pinned Privacy/Conduct/Advertise/About footer
- RightRail (now global on every page): ProgressCard (points+streak+next
  milestone from engagement.myStats) / JoinCard → trending tags → ConversionHub
  / NewsletterCapture
- CommandPalette: quick actions + live tag search (tag.search); posts/people
  via Algolia is a follow-up
- styles/globals.css: .app-topbar/.app-main/.app-leftrail/.app-rightrail grid
  with bespoke 1080/720 breakpoints
- feed/_client: drop internal rail+grid (rails are global now), render center
  column only; text-black→text-on-accent on the composer CTA
- (app)/layout: render AppShell; drop Algolia sidebar wiring

Old SidebarAppLayout/sidebar-layout/MinimalHeader/AppSidebar now unused.
Note: /discussions nav target lands in Phase 6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2. Per the handoff, the feed is the landing surface; there is no
marketing homepage.

- (marketing)/page.tsx: 308 permanentRedirect / -> /feed (canonical home).
  Drop the GradientBlinds hero / value-prop / feed-preview landing (the
  GradientBlinds component stays for reuse on /about).
- Logged-out visitors land on the public, indexable feed (getFeed is a
  publicProcedure) with the shell's sign-in bar + Join free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tion kinds

Phase 3.

- schema: add `til` + `question` to the post_type enum (migration
  0025_normal_nuke); extend ContentTypeSchema + toDbType/toFrontendType so
  QUESTION maps to a real `question` kind and TIL is first-class
- UnifiedContentCard: rebuilt as an editorial row — kind chip (Article/TIL/
  Question/Discussion/Link), author + @handle · time (· via source), display
  title with ↗ for links, 2-line excerpt, OG thumb / striped placeholder, mono
  #tag metadata, reaction bar (▲ helpful · replies · Save · Share with
  copy-link). Vote/bookmark/click mutations preserved; drop legacy chevron/
  bookmark-icon body and dead URL helpers
- feed: dismissible OnboardingBanner (first-win 3 steps, useSyncExternalStore)
  + low-bar Composer (Tip/Ask/Share/Write chips → /create?kind=)
- FeedFilters: add TIL option; feed type validation accepts `til`

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 4. Visual restyle of the three reader clients (user article, curated
link, user link) under app/(app)/[username]/[slug]/ — functionality, data,
voting/bookmark/share, FollowButton, and the comments component preserved.

- ‹ Back to feed ghost link, // eyebrow (tag/source · meta), large display
  title, author row + Follow, cover in a hairline frame / grid-dots placeholder
- mono editorial reaction footer; "Discussion {count}" header above comments
- swap all hardcoded neutral/blue/green/red + dark: colors for design tokens;
  text on accent is text-on-accent

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NiallJoeMaher and others added 4 commits June 12, 2026 21:35
…t resolvers

AI crawlers don't execute JS — the homepage, /discussions, /s/ pages and
member link posts previously shipped empty shells. The first page of each
list (and the full article/link bodies) now renders server-side and hands
off to the client queries as initialData; interactivity stays in client
islands. Adds an in-process tRPC caller (no HTTP self-fetch) and React
cache() on the per-request resolvers, halving DB round trips that
generateMetadata + page were paying twice; getServerAuthSession is now
request-deduped too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- app layout, checkBadges, and engagement procedures batch their
  independent queries with Promise.all; badge grants are one multi-row
  insert instead of a per-row loop
- new rateLimited middleware in trpc.ts; profile.edit/updateEmail,
  job.create, discussion create/edit/vote/follow, follow, tag.getOrCreate
  and comment edit/delete/vote are now throttled (previously unthrottled)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… screen wiring

- getRelativeTime consolidated from five drifted copies into
  utils/relativeTime.ts; ComposeModal uses getHostname; /s/ resolvers use
  parseUrlId; PostReader renders Markdoc once (cast gone); dead mirrored
  vote state removed from UnifiedContentCard
- SITE_ORIGIN/SITE_HOST in config/site.ts replace ~20 hardcoded
  www.codu.co literals (JSON-LD, sitemap, RSS, robots, IndexNow,
  canonical builders); buildCanonicalUrl and memberPostUrl now share
  buildContentHref instead of duplicating the URL scheme
- gatePublish runs the screenContent heuristic ahead of Bedrock review
  (was only the Bedrock-off fallback); cron lambda fails closed if the
  siteUrl SSM param is unreadable (was silently POSTing prod)
- tag pages get crawlable ?page=N pagination; llms.txt links top tag
  pages; footer stops linking redirecting legacy paths

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ti on every badge

- new onboarding_complete badge ('First Steps' 👣) granted when the three
  onboarding steps are done; inserted idempotently by the migration so it
  exists in every environment and shows in the achievements tab
- onboarding's third step is now 'leave your first comment' instead of
  publishing a post — finishing onboarding earns exactly one badge, and
  first_post stays locked as the next-step tease
- generic BadgeCelebration replaces OnboardingCelebration: every newly
  earned badge fires confetti + the unlock dialog (queued oldest-first),
  driven by a new user_badge.celebratedAt column (backfilled for existing
  badges so nothing retro-celebrates); firstWinCelebratedAt and its
  mutation are gone
- discussion.create (the live commenting path) now awards comment_created
  points like legacy comment.create did — it previously awarded nothing,
  so comments earned no points and never triggered badge checks
- follow and topics-save run checkBadges explicitly (no points awarded
  there); badge checks now also see comments, topics, and follow counts
- achievements tab shows '{n} earned' instead of '{n} of {total}' since
  the catalogue will keep growing

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NiallJoeMaher NiallJoeMaher marked this pull request as ready for review June 12, 2026 20:51
@NiallJoeMaher NiallJoeMaher requested a review from a team as a code owner June 12, 2026 20:51
NiallJoeMaher and others added 3 commits June 12, 2026 22:11
…en magic-link provider id

- docker-compose now ships Mailpit (host ports 1027/8027 — 1025/8025 are
  commonly taken locally); EMAIL_PROVIDER=local routes every outgoing
  email (magic links, moderation/report mail) to it instead of SES, in
  dev and E2E. Hard-disabled on production deploys.
- e2e/mailpit.ts helpers + email.spec.ts assert real delivery through
  Mailpit's API; the spec skips cleanly when Mailpit isn't running
- the new spec immediately caught a real bug: next-auth v5 registers the
  Nodemailer provider as 'nodemailer' while the sign-in UI calls
  signIn('email') — magic-link sign-in never sent anything. Provider id
  pinned to 'email'.
- editor e2e specs updated for moderator-only scheduling: the removed
  user-facing schedule controls are now asserted ABSENT instead of
  visible (3 stale tests were failing)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…suite

- npm audit: 53 vulnerabilities (1 critical, 13 high) → 8 (0 high/critical).
  next 16.1.1→16.2.9, drizzle-orm 0.39→0.45.2 (SQLi advisory GHSA-gpj5),
  drizzle-kit →0.31.10, constructs →^10.5, sentry + transitive bumps via
  audit fix; esbuild override ^0.28.1 lifts drizzle-kit's stale copies.
  Residual (documented): nodemailer 7.x moderate (next-auth peer-pins ^7;
  the vulnerable envelope.size param is never user-controlled here) and
  Next's internally-pinned postcss copy.
- drizzle 0.45 fixed the auth adapter type mismatch — stale
  @ts-expect-error removed
- e2e: badge celebrations can pop mid-test and block clicks — login
  helpers auto-dismiss via addLocatorHandler; saved.spec gets a dedicated
  bookmark fixture so it can't race other specs; local workers capped at
  4 (unbounded workers over-subscribed the dev server causing timeout
  flake — capped run is green 131/131 and faster)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- content.update({published:true}) on a scheduled/in_review/rejected post
  now throws like content.publish does — it was the one remaining path
  where an author could force-publish around a moderator's schedule or
  rejection
- SessionProvider now receives the server session, so session-derived
  query keys (feed 'following') are stable through hydration and SSR
  initialData attaches to the right key; discussions page mirrors the
  client's derivation exactly and skips the fetch when the client query
  would be disabled
- feed no longer hides published rows with NULL publishedAt (legacy/
  imported data)
- migration 0037 pre-grants the onboarding badge (already celebrated) to
  users who already qualify — without it every established member would
  get a nonsensical 'First Steps' confetti after deploy
- notification links to aggregated /s/ content resolve again (source slug
  included); ComposeModal routes 'View post' per content kind, refreshes
  the badge celebration, and its stale 'posted' comment is gone
- isLocalMail gates on NODE_ENV (covers non-Vercel prod + previews);
  sample.env no longer pre-sets EMAIL_PROVIDER=local; port comments match
  the 1027/8027 mapping
- suppressHydrationWarning on Date.now()-derived relative times in the
  now-SSR'd cards; dead SideBarSavedPosts (stale duplicate logic) removed
- ops docs: CRON_SECRET in sample.env, /env/siteUrl + /env/cronSecret SSM
  prerequisites in cdk/README, preview-deploy migration note in README

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@NiallJoeMaher NiallJoeMaher changed the title Relaunch: app shell, feed, create flow, onboarding, live search Relaunch: AI-builders repositioning — new shell & feed, URL/SEO restructure, moderation pipeline, achievements, hardening Jun 12, 2026
@vercel

vercel Bot commented Jun 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
codu Error Error Jun 13, 2026 6:28am

Request Review

NiallJoeMaher and others added 2 commits June 13, 2026 08:07
…the deploy

The /sitemap.xml route is the only one that queries the DB during prerender.
On Vercel preview/build (where the database isn't reachable at build time) the
unwrapped queries threw and aborted the whole build. Wrap every query in a
safeRows() helper that degrades to an empty list on failure; revalidate=3600
still regenerates the full sitemap at runtime once the DB is reachable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ter refs

- Reframe the intro from "community of web developers" to the relaunch
  positioning (AI builders & indie hackers), grounded tone, and surface the
  real post kinds (articles, TILs, discussions, questions).
- Replace stale Pages Router boilerplate (pages/index.tsx, pages/api) with
  accurate App Router guidance (app/ routes, app/(app)/page.tsx home feed,
  Route Handlers under app/api).
- Minor: Postgres "15.0" -> "15" to match docker-compose (postgres:15-alpine).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These files were committed unformatted; the PR head was a stale fork branch so
CI never checked them. Now that the branch is current, `npm run prettier`
(prettier 3.7.4 + prettier-plugin-tailwindcss class sorting) flagged 38 files.
Pure formatting — whitespace and Tailwind class ordering, no logic changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…T DISTINCT)

Migration 0029 adds `UNIQUE NULLS NOT DISTINCT` on point_event (the dedupe /
anti-gaming key), which is Postgres 15+ syntax. The RDS instance was pinned to
14.5, so `db:migrate` failed on every Vercel deploy. Bump the engine to 15.8
(local dev already runs postgres:15-alpine) and set allowMajorVersionUpgrade so
RDS accepts the major-version change.

NOTE: requires `cdk deploy` of the StorageStack to take effect (in-place RDS
major upgrade, brief downtime). Vercel stays red until that deploy completes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
RDS rejected 15.8 ("cannot find upgrade path from 14.22 to 15.8"); the valid
PG15 target from the live 14.22 instance is 15.17. The cdk subproject's
aws-cdk-lib (2.233.0) predates the VER_15_17 enum member, so use
PostgresEngineVersion.of("15.17", "15"). Verified on Dev: RDS accepted the
change and upgraded in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NiallJoeMaher and others added 2 commits June 13, 2026 20:17
…quirement

The Vercel Build Command is overridden to `db:migrate && ci-build`, so every
target migrates (preview->dev DB, prod->prod DB) and a failing migration reds
the build. Also document that connections need `sslmode=require` (rds.force_ssl).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rather than inheriting force_ssl from the default.postgres15 group (whose
default flipped 0->1 vs postgres14 and silently required SSL post-upgrade),
make it intentional in code. Share one pgVersion const between the instance and
the parameter group so they can't drift.

NOTE: deploying associates a new parameter group with the instance (needs a
reboot to finalize); behaviour is unchanged since force_ssl is already 1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NiallJoeMaher NiallJoeMaher merged commit 4678f5a into codu-code:develop Jun 13, 2026
5 of 6 checks passed
@NiallJoeMaher NiallJoeMaher deleted the feat/relaunch-repositioning branch June 13, 2026 19:52
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.

1 participant