Skip to content

Commit 4678f5a

Browse files
Relaunch: AI-builders repositioning — new shell & feed, URL/SEO restructure, moderation pipeline, achievements, hardening (#1331)
* feat(relaunch): reposition brand to AI builders & indie hackers 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> * feat(relaunch): add /about manifesto page 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> * feat(relaunch): add AI developer jobs board 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> * feat(relaunch): add 26 AI-focused RSS sources to the content feed 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> * feat(relaunch): add dev-only kitchen-sink design playground 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> * feat(relaunch): wire relaunch fonts globally + rebuild kitchen sink as 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> * feat(relaunch): design tokens in Tailwind theme + reusable ds components 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> * feat(relaunch): rebuild homepage hero + funnel capture (content-as-funnel) 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> * feat(relaunch): dark-only + new full-bleed marketing layout & homepage 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> * feat(relaunch): richer homepage feed cards with cover previews 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> * feat(relaunch): distraction-free auth in a bare (auth) layout + keep 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> * feat(relaunch): rebuild /about in the marketing layout 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> * feat(relaunch): refresh app sidebar to tokens + replace gradient newsletter 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> * feat(relaunch): refine marketing nav + record IndieHackers learnings - 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> * feat(relaunch): rebuild /advertise in marketing layout, recolor contact 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> * feat(relaunch): kill orange/pink across the app + soften feed card hover - 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> * feat(relaunch): add IndieHackers-style conversion hub to the feed rail 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> * feat(relaunch): engagement loop — points, daily streak & Build Board 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> * feat(relaunch): retokenize feed/article cards + simplify feed taxonomy + 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> * feat(relaunch): premium ambient hero (aurora + constellation + reveal) - 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> * feat(relaunch): badges/achievements (schema, award logic, profile display) - 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> * feat(relaunch): WebGL GradientBlinds hero (themed mint) replacing the 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> * feat(relaunch): referral / invite loop → points + Connector badge - 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> * feat(relaunch): social platform — follow graph, Following feed, feed-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> * feat(relaunch): follow notifications, achievements view, followers/following 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> * feat(relaunch): expand design tokens to full handoff system 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> * feat(relaunch): app shell — 3-col rail grid, top bar, ⌘K palette 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> * feat(relaunch): feed is the home — retire marketing landing 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> * feat(relaunch): editorial feed rows, reaction bar, composer, TIL/Question 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> * feat(relaunch): editorial article reader + discussion thread 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> * feat(relaunch): write → auto-moderation → In review flow (env-gated) Phase 5. Gated behind MODERATION_ENABLED (default off) — publish behaves exactly as before unless enabled. - schema: add in_review + rejected to post_status enum (migration 0026) - server/lib/moderation.ts: isModerationEnabled() + dependency-free screenContent() heuristic (excessive links / too short / spam keywords) - publish (content.ts + post.ts): when enabled, a first-time publish routes draft → in_review (no publishedAt, no points) instead of published - admin.ts: listInReview + moderatePost (approve → published + publishedAt + points award + author notification; reject → rejected; guarded to in_review) - POST_APPROVED notification type wired through notification router + UI - admin/moderation: In-review queue with Approve/Reject; my-posts: In review / Rejected owner badges; editor: "Sent for review" toast when in_review Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): Discussions surface Phase 6. New /discussions route (the top-bar/left-rail nav target). - content getFeed: optional `kinds` array filter (posts.type IN (...)) so a surface can show multiple kinds; schema GetUnifiedFeedSchema gains `kinds` - app/(app)/discussions: community-framed page — eyebrow + title + "Start a discussion" CTA, intro copy, popular-tag topic chips, and the feed-style row list filtered to discussion + question kinds with infinite scroll Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): profile — gradient banner, tabs (Posts/Achievements/About) Phase 7. Gradient banner + overlapping ring avatar, name + conditional Top-helper chip, @handle/role + bio, Posts/Followers/Following stats, and real Posts / Achievements / About tabs. Achievements: points + streak cards + earned/locked badge grid from engagement.profileEngagement. Source profile header restyled to match. All queries/mutations/follow/admin controls preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): restyle jobs, notifications, saved, settings Phase 8. Editorial restyle, functionality preserved. - settings: single 620px column, mono eyebrow sections, custom mint toggle, danger Delete account - notifications: bordered row list, unread tint, mark-as-read kept - jobs: SimpleHeader + Post a job, mono filter pills, Featured + All groups, JobCard (company glyph, role, meta, stack tags) - saved: eyebrow header + editorial saved-item rows Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): auth split-screen + email provider gating Phase 9. get-started becomes a split screen: brand panel (grid-dots, headline) + form panel with GitHub primary / GitLab secondary. Bare (auth) layout (no duplicate logo/chrome). Email magic-link UI gated by NEXT_PUBLIC_EMAIL_AUTH (or dev); auth.ts registers the Nodemailer provider only when EMAIL_AUTH_ENABLED (or dev) — GitHub/GitLab always on for spam control. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): info pages — slim back-bar, About manifesto, Advertise tiers Phase 10. MarketingNav → slim "‹ Back to feed" bar + logo; footer → mono cross-links. About rebuilt as the manifesto (lede, craft/why, founder note, three personas, Codú Weekly band, CTAs). Advertise → three honest tiers + hello@codu.co. Privacy & Code of conduct legal text preserved verbatim — only the chrome (eyebrow header + prose container) re-tokenized. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): a11y polish + e2e spec updates Phase 11 (final). - globals.css: global prefers-reduced-motion block (neutralizes animations/ transitions) mirroring the handoff base.css; GradientBlinds already guards rM - CommandPalette: arrow-key nav + Enter-to-select + roving aria-selected options (role=listbox/option, combobox input); mounted only while open so state stays fresh without a reset effect; AppShell captures the trigger and restores focus on close (⌘K toggles, Esc closes) - UnifiedContentCard: aria-label on the helpful control - e2e: home.spec (→ feed redirect), login.spec (new split-screen headings + Codú-home link, authed → /feed), feed.spec (helpful-only reaction bar, rail discovery), notifications.spec (lower-case empty copy) Note: specs updated to match the new UI; the Playwright suite was not executed here (needs the ENV=E2E server + seeded DB + auth setup). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): enable moderation + email admin; design-review shell fixes - moderation: MODERATION_ENABLED on (local .env, gitignored); notifyAdminOfReview emails ADMIN_EMAIL (via existing sendEmail/SES) when a post enters in_review, wired into content.publish + post.publish (fire-and-forget) - design review vs handoff: onboarding "first win" steps 3→2 columns; left-rail nav is text-only (the system "leans on typography, not a heavy icon set") — dropped the Heroicons; active nav uses aria-current + the surface treatment Note: dev server must restart to pick up MODERATION_ENABLED. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): restyle the article editor and admin panel - editor (app/(editor) + components/PostEditor): mint .primary-button Publish (was a washed-out green), display-font title, feed-style Write/Link tabs, tokenized toolbar/tag-input/More-Options, // mono eyebrow labels - admin (app/(app)/admin/*): eyebrow + display headers, mint-underline section tabs, status pill chips on the success/warning/danger/info washes, tokenized tables/forms/buttons; moderation Approve/Reject queue kept functional Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): backend foundations — user topics, live search, rate limiter R2-A. - schema: user.topics (text[]), experienceLevel, onboardedAt (migration 0027) - profile router: myInterests query + updateInterests mutation (capped/deduped, optional experienceLevel + markOnboarded) - search router: search.everything — live posts + people + tags search to replace Algolia; min query length, hard result caps, and a per-user/IP sliding-window rate limit (30/10s) - server/lib/rateLimit.ts: in-memory sliding-window limiter + client-IP helper Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): new-member onboarding flow + auth routing/copy R2-C. Full-screen 3-step onboarding (interests → experience → focus → done) at /welcome, outside the app shell; saves via profile.updateInterests (markOnboarded). NextAuth pages.newUser → /welcome so signups land in it (returning users unaffected). get-started brand copy refreshed to "where coders become product engineers / Build the right thing. And build it well." Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): advertise contact modal + drop newsletter price R2-F. Advertise tiers/CTA open a contact modal (name/email/company/message → existing sponsor.submit inquiry) with a sonner success + hello@codu.co fallback. Newsletter slot price replaced with "Get in touch for rates"; Stripe-backed job tier pricing left untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): richer seed data for relaunch readiness R2-G. ~108 users with topics/experience/onboardedAt; curated posts across all kinds (article/discussion/question/til/link/resource) and statuses (published/in_review/draft) with varied votes/dates for sort; tags + post_tags, follows, bookmarks, votes, jobs, and engagement (points/streaks/badges). Preserves the existing idempotent truncate-then-insert seed strategy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): 3-col top bar, single Create entry point, edit-topics R2-B. - TopBar: 3-column grid (logo · centered search · right group); drop the center Feed/Discussions/Jobs nav (lives in the left rail); "Write" → "+ Create" - ShellActionsProvider (context): openCompose/openTopics + renders the modals; Create is gated on first use by a one-time dos-&-don'ts CreateInfoModal (localStorage) - ComposeModal: Discussion/Link create immediately via content.create (+ "you're live" confirm), Article tab hands off to the /create editor - TopicsModal: edit "Your topics" via profile.myInterests/updateInterests - LeftRail: Your topics from the member's interests + Edit (popular tags when logged out) - feed: drop the inline composer (Create is the single entry point); Discussions "Start a discussion" opens the compose modal Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): flat feed filters + live ⌘K search R2-D + R2-E. - Feed filters: flat borderless FilterPill (Type · Sort · Topic) on the right of the For-you/Following tabs — no icons/boxes, ✓ on selected, "clear" when dirty; real URL-param filtering + the three backend sorts. Articles page adapted to the new props (tag/topics/onTagChange/onClear). - ⌘K palette: wired to search.everything (debounced 300ms) — Posts / People / Tags groups with arrow-key nav; Quick actions when empty. Replaces the Algolia path (the dead Algolia SearchBar/MinimalHeader are no longer reachable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(relaunch): update feed filters spec + add search/onboarding/create e2e R2-H. - feed.spec: migrate to the flat filters (testid triggers → role=option, "All types" label); URL assertions unchanged - search.spec: ⌘K palette opens, live results / no-matches, Esc closes (public) - onboarding.spec: /welcome redirects to /get-started logged out; authed shows step 1 or redirects to feed - create.spec: + Create → dos-&-don'ts gate → compose modal tabs (Link URL field), closes without posting Suite not executed here (needs ENV=E2E + seeded DB + auth); specs verified via tsc + eslint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): address self-review (mobile nav, discussion 404, write spam) Review fixes before PR: - H1: discussion/question/til/resource posts now resolve in the [username]/[slug] reader (getUserPost broadened from article-only); ComposeModal builds the canonical /{username}/{slug} URL (threaded username) instead of the legacy /articles redirect that 404s for new posts - H2: add a bottom MobileNav (≤720px) so mobile users can reach Home/Discussions/ Jobs/Notifications/Profile + Create; hide the sign-in bar on mobile - H3: rate-limit content.create when published (10/5min per user) — the quick compose path can no longer be scripted to flood the feed - H4: rate-limit + IP-throttle the public sponsor.submit (3/hr per IP) - M1: escape LIKE metacharacters in search to prevent wildcard scans - L1: sponsor email subject falls back to name when company is omitted Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): null-slug duplicate keys + pink buttons - tags with null slugs produced duplicate React keys (key=null) + broken /feed?tag=null links: getPopular/tag.search now filter isNotNull(slug), content.create always generates a tag slug, rails use a key fallback - add a mint `accent` color to the Catalyst Button and swap the leftover color="pink" buttons (settings email/save, jobs/create) to it Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): seed cover images + tag slugs; refresh email copy - seed: every tag gets a slug (fixes trending links/keys); curated + sample posts get deterministic picsum coverImage URLs so feed previews render (43 with cover, some left coverless to exercise the placeholder) - email copy refreshed to the relaunch voice (verification, magic-link, admin review) — token/URL wiring untouched Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(infra): DynamoDB-backed rate limiting + CDK table Per the AWS-native stack (matches SES). server/lib/rateLimit.ts now has one async API over two backends: - DynamoDB (when RATE_LIMIT_TABLE is set): atomic fixed-window counters with native TTL; fail-open on errors so a misconfigured table can't take the site down. AWS SDK clients added (@aws-sdk/client-dynamodb + lib-dynamodb). - in-memory sliding window for local dev / fallback. Helper `enforceRateLimit({ key, limit, windowMs, message })` throws TOO_MANY_REQUESTS; search / content.create / sponsor.submit now await it. CDK: StorageStack provisions the rate-limit DynamoDB table (pk + ttl, PAY_PER_REQUEST, retain on prod) and publishes its name to SSM /env/rate-limit-table + a CfnOutput. App env needs RATE_LIMIT_TABLE = that name and dynamodb:UpdateItem on the table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): mint top loader, kill stray pink, drop empty image box - top loading bar: orange→pink HolyLoader gradient → mint (accent→accent-soft) - prism code highlighting: .token.function/.class-name text-pink-500 → text-accent-soft (the last visible pink in the app) - feed card: render the thumbnail only when a real image loads — no grey dotted placeholder box when an image is missing or fails to load Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): feed image top-right, mint focus rings, AA contrast bump - feed card: preview image moved to the top-right (aligned with the header), matching the design — header+title+excerpt now share a left column with the thumbnail as a top-aligned sibling - focus states: Catalyst primitives' blue focus outlines/rings → mint (accent); listbox/dropdown active item → bg-accent/text-on-accent - contrast: --color-faint #757e8c → #868f9b (clears WCAG AA >=5.2:1; old was 4.49:1); feed filter "·" separators border-strong → faint Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): settings tabs, profile cleanup, auth centering (diff #3) - settings: Profile · Notifications · Account tabs replace the stacked mono eyebrow sections (tabs stay mounted so RHF state survives); sticky save bar - profile: drop the gradient banner + About tab — header is avatar + name + Top-helper + @handle, a mono meta row (joined/location/website), bio, and the user's topics as tags (wired via user.topics); Posts · Achievements only - auth: cap the split-screen to a centered max-w-[1080px] on ultrawide; "or with email" divider no longer wraps Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): track onboarding wins + wire missing engagement awards - feed filters: drop the "·" separators, swap the ▾ triangle for a proper down-chevron SVG - onboarding banner: real completion via engagement.onboardingWins (topics set / 3 follows / first post) — done steps show a mint check + strikethrough + progress rail; banner hides once all three are done; steps open the topics/ compose modals (work on mobile too) - engagement gaps closed in content.* (the relaunch compose/vote path never awarded): upvote_received → post author (idempotent per voter), post_published on content.create (publish) and content.publish (first publish). daily_active (streaks), comment_created, referral already wired; checkBadges runs after each. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): responsiveness pass — hamburger + slide-out NavDrawer (diff #4) - TopBar: mobile hamburger (.nav-burger ≤720px) + flexible grid (auto/1fr/auto) so the centered search shrinks instead of overflowing; ⌘K kbd hidden ≤560px - NavDrawer (new): left slide-out drawer (panel + fading backdrop, Esc/backdrop/ link-close) holding nav + Your topics + footer links — replaces the left rail ≤720px. Removed the bottom MobileNav tab bar (+ .app-mobilenav CSS); sign-in bar shows on mobile again - Settings: Topics editor in the Profile tab (chips + Edit → TopicsModal) so topics are reachable on mobile - Auth: split collapses to one column ≤860px (brand panel hidden, logo above form) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): tokenize inputs, settings dividers/theme, feed tab spacing - Catalyst input/textarea/select: dark styles → tokens (bg-inset, border-hairline /strong, text-fg, placeholder text-faint) so form fields match the design instead of the washed-out white/5 default - settings: drop the trailing section divider (last:border-b-0) so it no longer doubles up with the sticky save bar; remove the light/dark theme toggle (the app is dark-only) + its now-unused imports/group label - feed tabs: more breathing room (gap-x-7, flex-wrap), aria-current on the active tab, clear gap before the card list Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): compose input polish + link card preview; drop editor Link tab - ComposeModal: title is truly borderless (no browser-default box); URL/body/tags use bg-inset + hairline, mint focus, no harsh outlines - Link tab: feed-card-style preview — OG header image over the title, then domain + the user's take. Reuses the existing /api/fetch-metadata via useLinkMetadata (debounced); placeholder when none yet - editor (/create): removed the redundant Write/Link tab switcher — links are created in the compose modal now; opens straight into the article editor. Article flow (TipTap, preview, tags, canonical, publish/moderation) intact Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): feed is the homepage at "/" (not /feed) - new app/(app)/page.tsx renders the feed at "/"; removed the (marketing)/page.tsx redirect; /feed now 308s to "/" preserving query (?tag/?type/?view/?sort) - repoint all homepage links (logo, Home nav, For-you/Following tabs, filters, ⌘K "Go to Feed", marketing nav, back-to-feed, tag chips) from /feed → "/"; /feed/[sourceSlug] detail routes unchanged - NavDrawer (mobile): drop "Your topics" (editable in Settings on mobile); tidy isActive Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): tokenize feed error/empty states + loading skeleton Old light-mode classes (red-50/neutral-* + dark: pairs) replaced with tokens; the loading skeleton now mirrors the real card (header + title/excerpt left, preview top-right) using bg-elevated bars. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): tokenize remaining old-style surfaces my-posts, draft preview, auth/verify, forbidden, metrics, speakers — swap light-mode neutral/white/black/red + dark: pairs for design tokens (bg-surface/ canvas, text-fg/muted/faint, border-hairline, text-danger, font-display). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): redesign discussion thread + fix duplicate header Restyle the comments thread (DiscussionArea + WYSIWYG editor/toolbar) to the relaunch design — token-styled comments, reply composer, nested replies; drop DiscussionArea's own heading so the reader's single "Discussion {N}" header stands (no more double header). Functionality (create/edit/delete/vote/replies) preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): tokenize Catalyst ui-component primitives to the dark theme Restyle the full Catalyst primitive set to design-system tokens (bg-surface/ elevated/canvas/inset, text-fg/muted/faint, border-hairline/strong, status washes, mint accent): dialog, dropdown, listbox, alert, badge, pagination, table, fieldset, heading, text, divider, description-list, avatar, navbar, sidebar, sidebar/stacked-layout. Form controls (checkbox/radio/switch) now default to a mint checked state; button/outline text uses cool text-fg, not pure white. Structure/props/Headless wiring unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): tokenize remaining live surfaces Reader action bars (ArticleActionBar + ContentDetail ActionBar/MetaHeader/ SourceInfoCard/TypeBadge), the global ReportModal + PromptDialog, the source-feed + job detail pages, and the speaker/volunteer forms — swap light-mode neutral/ white/black + blue/red/green + dark: pairs for design tokens (bg-surface/inset/ elevated, text-fg/muted/faint, border-hairline/strong, mint accent, status washes). Helpful/upvote → success/accent, bookmark-active → accent, report/ destructive → danger. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(relaunch): remove dead code orphaned by the redesign The relaunch superseded a cluster of components that now have zero live importers — removing them as a follow-up to the restyle: - old shell/nav/search: components/Nav, components/Header (MinimalHeader), components/ui/Search, components/Layout/SidebarAppLayout - legacy feed/content: components/Content (ContentCard), components/Feed AggregatedItem/VoteButtons/Composer/PopularTagsSidebar, components/PopularTags - superseded by the new surfaces: components/Comments (→ DiscussionArea), components/ArticleSearch + components/ArticleMenu (→ ⌘K palette / new reader), PostEditor PostEditor.tsx + tabs/LinkTab + components/UrlMetadataPreview (route uses WriteTab; links go through the compose modal) - app/(app)/articles/_client.tsx (the /articles route now redirects to /?type=article) Barrels (Feed/index.ts, PostEditor/index.ts) pruned to the surviving exports. tsc + eslint clean; / · /discussions · /jobs · /speakers · /volunteer all 200. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): standalone form layout + mint radios; faster e2e default - AppShell: /speakers + /volunteer render rail-less (centered single column) — they're standalone forms, not feed reading surfaces - radios on those forms now tint mint via a dedicated .accent-mint utility (Tailwind's arbitrary accent-[...] inference was unreliable for accent-color; verified computed accent-color = rgb(45 212 191)) - playwright: default to Desktop Chrome locally (~4× faster); full cross-browser + mobile matrix runs on CI or with ALL_BROWSERS=1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(relaunch): fix specs for new routing; custom mint radios - e2e: feed-is-home — "/" renders the feed (no redirect), /feed→/; get-started authed → "/"; feed/create specs use "/"; notifications card class → border-hairline - radios: fully custom appearance:none control (mint ring + dot, mint focus outline). accent-color is ignored by Chrome under color-scheme:dark, so the native control kept painting UA blue regardless of value. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(relaunch): align notifications comment-flow with redesigned thread Wait for the "Add to the discussion…" composer (the redesigned DiscussionEditor trigger) instead of a "Discussion (N)" heading, and post via it. The reader that renders user-authored posts has no discussion section header — noted for review. All 37 relaunch feature specs green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(relaunch): richer seed + specs for discussions, follow graph, profile Seed (e2e/setup.ts): a published discussion + question by user one (with a top-level comment + nested reply), user two → user one follow, and user one's topics / experience / referral code / point events. All cleaned via the user-delete cascade. New specs: - discussions.spec: discussions listing + Start CTA; thread renders the post + seeded comment/reply; signed-in reply composer opens - follow.spec: Following feed surfaces followed users' posts; profile follower/ following stats; follow control reflects the existing relationship - profile.spec: name + interest tags + Posts/Achievements tabs (no About); Achievements surfaces points/streak Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(relaunch): onboarded /welcome redirect lands on "/" not /feed Seed marks user one onboarded, so /welcome bounces to the feed homepage; widen the URL assertion to accept "/". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(relaunch): modernize editor/articles/saved specs for the new UI - editor.spec: delete the obsolete Link-tab + tab-switching + link-publish/ validation blocks (the editor lost its Link tab; links go via the compose modal). Fix the article-editor tests for the no-tabs editor; publish flow accepts the in_review redirect (MODERATION_ENABLED in e2e) - articles.spec: point at "/?type=article"; drop tests now covered by feed.spec or asserting the removed sidebar; keep article-detail nav, comment/cannot- comment, write-and-publish (via compose → Article → editor) - saved.spec: bookmark via the feed-card Save action + reader Save; new empty- state copy; serial + retries to absorb shared-bookmark-state parallel flake Full suite: 117 passed on Desktop Chrome. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(relaunch): trim verbose comments to clean developer docs Collapse decorative section banners, redundant JSX labels, and tutorial-voice narration across 44 relaunch source files; keep the non-obvious WHY/gotchas (fail-open engagement, moderation gate, dedupe-index anti-gaming, accent-color under color-scheme:dark, handoff mirrors, etc.). Comments only — code unchanged; tsc + eslint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: gitignore local.md (local-only deploy notes) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): VoteControl pill — up/score/down voting (handoff #5 §1) New components/Vote/VoteControl.tsx: a hairline pill (up-chevron · net score · down-chevron, inline-SVG chevrons, tabular-nums). Up active → accent-soft, down → danger; hover previews the arrow colour; clicking the active arrow clears the vote; stopPropagation so it never opens the row. Wired (reusing existing mutations) into the feed card reaction bar (content.vote), discussion comments/ replies (discussion.vote), and the article reader footer (post/content vote) — replacing the old "▲ helpful" upvote-only control. Outline variant only (the prototype's tinted variant + tweaks panel are prototype-only). feed.spec updated to the Upvote/Downvote labels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): markdown rich-text editor + toolbar (handoff #5 §3) New components/RichText/: useRichText hook + selection-aware markdown transforms (bold/italic/strike/clear/link/ul/ol/quote/code/table), RichToolbar (inline · lists · block groups with dividers + "Switch to Markdown"), AaToggle, and MdTextarea (autosize). Storage stays markdown; the toolbar wraps/inserts md around the textarea selection (onMouseDown preventDefault keeps selection). Replaces the TipTap comment/reply editor (deleted its Toolbar/useEditor/ extensions/help-modal) with this markdown composer — keeps the collapsed trigger + onSubmit(markdown)/submitLabel contract so DiscussionArea is unchanged — and upgrades the ComposeModal body field. The TipTap article editor is untouched (articles keep WYSIWYG). e2e comment-flow specs type into the new textarea. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): onboarding celebration — confetti + badge unlock (handoff #5 §4) New components/Celebrate/: Confetti (canvas burst, mint/status palette, ~1.7s, early-returns under prefers-reduced-motion) and BadgeUnlock (full-screen dialog, hexagon badge tile with codu-badge-pop + ring-pulse, "// badge unlocked", +points, Keep browsing / See your badges → profile achievements). Fires once when the real onboarding wins all complete — guarded by a localStorage flag (read via useSyncExternalStore) + a close flag, trigger in an effect (no setState-in-effect). username threaded through ShellActions for the badges link. Keyframes added to globals (motion gated on prefers-reduced-motion). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): followable Publications (handoff #5 §2) Publications = feed sources, now followable. New publication_follow table (userId↔sourceId, migration 0028) + publication tRPC router (getBySlug with follower/article counts + isFollowing, follow/unfollow). The source profile at /{slug} (_sourceProfileClient) is redesigned to the publication layout: square logo tile, "// publication" eyebrow, name + @handle, Follow/Share, tagline, Followers + Articles stats, "// latest articles" via UnifiedContentCard — no banner. Byline "in {Publication}" links (card + reader MetaHeader) point to /{slug}. (The /feed/[sourceSlug] route is 308-redirected to /{slug} by next.config, so the canonical page lives there.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): dedupe points by NULLS NOT DISTINCT (review H1/H2) point_event_dedupe was a unique INDEX with default NULLS DISTINCT, so only upvote_received (non-null actor) ever deduped — post_published/comment_created/ daily_active/referral all passed actorId NULL and never conflicted, silently disabling idempotency (farmable points; republish re-awarded +20). Converted to a UNIQUE constraint with NULLS NOT DISTINCT (migration 0029 dedupes existing rows first, correcting any farmed points). post_published is now once per post. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): comment rate limit, no self-comment points, "edited" bug (review M2) - enforceRateLimit on comment.create (10/min per user) — anti-spam/farm - don't award comment_created when commenting on your own post - fix the false "edited" badge: the post-insert ltree path write tripped the updatedAt $onUpdate, so every new comment rendered as "· edited"; pin updatedAt back to createdAt on that internal write Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(security): close remaining review findings (H3, M1/M3/M4, L1–L4, L6/L7 + UI) Security/integrity: - H3: moderation in_review gate now applies to create/update (not just publish) in content.ts + post.ts — no more self-publishing around review - M3: publish mutations share the create rate limit (10/5min) - M1: per-referrer daily cap (5/24h) on referral points - L3: removing/downgrading an upvote revokes the author's upvote_received points - M4: newsletter subscribe action throttled by IP (3/hr) - L2: Sentry warning when RATE_LIMIT_TABLE is unset in production - L4: HTML-escape title/authorName in the moderation admin email - L1: document the new env vars in sample.env UI/feature: - discussion sort → feed FilterPill style (Top/New/Oldest); comment "···" menu + other action menus restyled to the relaunch popover pattern (bg-elevated/ border-strong/shadow-pop, danger destructive) - M5: onboarding "focus" step optional + de-overpromised copy - M7: Jobs nav entries gated behind the JOBS flag (rail/drawer/palette/hub) - M8: "Discussion {N}" heading on the user-post reader - L6: single <h1> on profile pages; L7: BuildBoard mounted in the right rail behind its flag Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): allow seed avatar/image hosts; feed header + chips; query-param tabs - next/image: allow robohash.org + picsum.photos + i.pravatar.cc (seed/e2e avatars + covers were throwing "hostname not configured" and breaking pages) - feed: remove the big "Feed" h1, float the For-you/Following tabs + filters to the top to reclaim space - cards: unify kind chips — all soft corners (rounded-sm) with an opaque token wash per kind (Discussion/Question no longer look offset; Link no longer a pill) - settings + profile tabs are now ?tab= query params (deep-linkable, URL-as-state) - home.spec updated for the removed heading Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(relaunch): remove BuildBoard (legacy) + fix feed specs for removed header - BuildBoard was orphaned legacy content that doesn't fit the relaunch — removed the RightRail mount, the component, its barrel export, and the now-unused engagement.leaderboard endpoint - feed.spec: drop the "Feed" h1 assertions (the heading was removed) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(relaunch): follow discussions + All/Following tabs + sort filter Discussions page drops the topic-tag row for the feed pattern: All / Following tabs (deep-linkable via ?view=) + a FilterPill sort (Recent/Active/Top). New post_follow table (migration 0030) + discussion.follow/unfollow/isFollowing; a +Follow ⇄ ✓Following toggle on the thread. New comments notify the post's followers (NEW_COMMENT_ON_FOLLOWED_POST, de-duped vs author/parent/commenter), rendered on the notifications page. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(relaunch): trim comment noise from handoff #5 + fixes Remove redundant JSX banners, numbered group labels, and narration across the recently-changed files; keep the WHY/gotchas/security rationale. Comments only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(relaunch): onboarding detects feed via For-you tab; bump editor schedule timeout - onboarding spec: the removed "Feed" h1 broke feed-detection on the onboarded redirect — detect the feed by its "For you" tab instead - editor schedule-modal wait 5s→10s to absorb full-suite parallel-load flake Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * style: prettier-format the relaunch + restore load-bearing eslint-disable Run prettier --write across the relaunch/handoff-#5/fix changes (the format check was failing CI), auto-remove genuinely-unused eslint-disable directives, and restore the one disable lint:fix wrongly stripped (the synchronous setSelectedIndex in slash-command's useLayoutEffect). eslint 0 errors, prettier check passes, tsc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): discussion reader on the canvas; opaque bordered type chips - user discussion/link/article readers no longer wrap content in a warm dark:bg-neutral-900 card — they render bare on the black canvas like the source-article reader (also tokenized the meta row + tag pills in those branches) - type chips (UnifiedContentCard KIND + ContentDetail TypeBadge) are now opaque bg-elevated pills with a per-kind colored border + label, so Link/Discussion/ article/etc. read as anchored bordered pills instead of floating text Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(cdk): remove CodePipeline, add direct-deploy entrypoint + app IAM stack Drop the self-mutating CodePipeline (PipelineStack) in favour of direct per-account deploys. Infra changes are rare enough that the manual two-step (assume the target account's creds, cdk deploy) is simpler and removes the pipeline's standing cost. - New canonical entrypoint bin/cdk-codu.ts: account/region from the active credentials (CDK_DEFAULT_ACCOUNT), DEPLOY_STAGE=Dev|Prod selects stack names + production flag. Set as the cdk.json app. - Remove lib/pipeline-stack.ts and the old bin entrypoints. - New lib/iam-stack.ts: the IAM user the Vercel app authenticates as, with least-privilege grants (S3 bucket read/write, dynamodb:UpdateItem on the rate-limit table, ses:SendEmail). Wired into AppStage next to the resources it grants, so future app grants have an obvious home. - Add cdk/.gitignore for tsc build artifacts; ignore env.aws (holds the generated app access keys — never committed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: remove unused Algolia integration Site search moved to the DB-backed search router (server/api/router/search.ts), so the scheduled Algolia indexer is dead weight. - Drop AlgoliaLambda + its daily rule from CronStack and delete the algoliaIndex lambda. - Remove algoliasearch + @algolia/autocomplete-* deps and the algolia:test script (its target script was already gone). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(cdk): clean up dead pipeline/Algolia leftovers Tidy bits left unused after removing the CodePipeline and Algolia: - Drop the two dead SSM accountId lookups from cdk.context.json (only the pipeline read them; also removes the tooling account id from the file). - Rename the package to cdk-codu and drop the bin entry that pointed at the deleted pipeline entrypoint. - Remove the commented-out placeholder test; fix jest.config (it pointed at the now-deleted test dir) so `npm test` passes with no tests. - Replace the boilerplate CDK README with one describing the actual stacks and the direct-deploy workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(relaunch): security, correctness & cleanup from pre-review pass Security - Block javascript:/data: hrefs from user URLs: new safeExternalHref() guard applied to profile website, job apply, and link/feed external URLs; tighten zod (httpUrl()) on externalUrl/applicationUrl/websiteUrl (z.string().url() accepts those schemes). Fix target="blank"/missing rel. - Escape user free-text in the job-listing admin notification email. - Rate-limit + gate-on-published the unauthenticated trackView/trackClick counters so they can't be scripted to inflate trending/popular. - Lock job.markPaid to admin-only (was owner-callable → free listings). - Scope CronStack SSM policy to /env/* instead of "*"; give it StackProps. Correctness - Fix broken relative article links (notifications, my-posts) and the post-login default redirect (/articles no longer exists → "/"). - admin.getUsers: replace broken keyset cursor with offset pagination. - discussion.create: pin updatedAt so new comments don't show as "edited". - discussion.get: scope user-vote lookup to this post's comments. - post.vote: award/revoke upvote points to match content.vote (idempotent via the dedupe index, so dual paths can't double-count). - recordDailyActivity: onConflictDoNothing to survive the first-activity race. - profile.edit: whitelist columns instead of spreading input. - job.moderate/setFeatured: NOT_FOUND when the id matches nothing. - migration 0029: drop the dedupe index/constraint defensively (IF EXISTS). Cleanup / DRY - Consolidate duplicated ensureHttps/getFaviconUrl/getHostname into utils/url.ts; share TOPIC_POOL between compose and onboarding. - Remove dead StreakBadge, unused BUILD_BOARD flag and publishData; drop the hourly sitemap Sentry.captureMessage debug; normalise isFlagEnabled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(email): never send real email in test/E2E The shared Nodemailer transporter (used by both sendEmail() and the next-auth magic-link sender) now switches to a no-op jsonTransport when ENV=E2E (the dev:e2e server), NODE_ENV=test, or MOCK_EMAIL=true — so Playwright runs that publish posts, create jobs, report, etc. no longer hit SES and spam real inboxes. Mocking at the transport covers every outgoing email path in one place. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(relaunch): cut comment noise across the new code Remove restating/section-divider comments and condense verbose blocks in the relaunch routers, libs, components and pages, keeping only the non-obvious "why" notes (DB triggers, dedupe idempotency, rate-limit and moderation rationale, responsive/accessibility intent). Comments only — no behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(auth): full-bleed brand panel on get-started, content centered in columns Drop the 1080px cap so the two-column split spans the viewport — the grey brand panel now bleeds to the left edge. Content sits in a centered max-width column on each side (a touch wider on the left). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(relaunch): sweep old-theme color leaks & retune button sizing Make .primary-button/.secondary-button compact by default (px-3.5 py-1.5) and add an opt-in .btn-jumbo modifier for hero/marketing CTAs. Tokenize the disabled state and drop the ad-hoc padding overrides on Create/Publish/Join free so the chrome buttons share one size. Replace leaked old-theme colors with relaunch tokens across the dark surface: switch track, tiptap/editor placeholders, nav-button, dropdown-*, modals, content-detail/bio, loading skeletons, footer, headings, tabs, verify-email, code-block copy buttons, and the live article renderer (render-extensions). Neutralize all social-link hovers to bg-elevated, drop the orange source-feed avatar gradient, and remove the dead github/twitter/discord/youtube palette entries. Fix a bare `neutral-900` class (missing text- prefix) in BioBar. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(relaunch): remove legacy courses feature Delete the mock-data-only courses routes and components ahead of pruning the legacy routes: app/(app)/courses, app/(app)/alpha/courses, and components/Course. Drop the now-unused COURSE_VIDEO feature flag. The feature had no DB tables, tRPC router, or navigation links, so nothing else references it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(moderation): brainstorm design + implementation plan Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(test): add vitest for unit tests Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(db): moderation columns, post report FK, pg_trgm Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(url): add getAppOrigin() origin-only base-URL helper Origin-only (scheme+host, no path) base for app-facing email links. NEXTAUTH_URL carries a /api/auth path that must be stripped. TDD'd. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(email): moderation link points to /admin/moderation page (was /api/auth/...) NEXTAUTH_URL is the callback form (.../api/auth), so the old base produced .../api/auth/admin/moderation (404). Use getAppOrigin() and deep-link to the queued item: <origin>/admin/moderation?item=<postId>. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(email): de-duplicate base-URL builders onto getAppOrigin() Drop two byte-identical local getBaseUrl() copies (report router + article report template) in favour of the shared origin helper. Link paths unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat: normalizeUrl for link dedupe Normalises external URLs for dedupe: lowercases host, strips www and trailing slash, drops tracking params (utm_/ref/fbclid/gclid/mc_/igshid) and fragments, returns null for non-http(s) input. TDD with Vitest. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(moderation): freshness + similarity dedupe helpers Pure isWithinFreshnessWindow predicate (freshness.ts, no DB import, unit tested incl. the 6-month inclusive boundary). DB-backed query helpers in dedupe.ts: findFreshDuplicateLink (normalized URL, published, within window) and findSimilarDiscussion (pg_trgm similarity on lower(title) via the GIN index, > 0.5 candidate threshold; callers treat >= 0.8 as hard dup, 0.5-0.8 as route-to-review). Not wired into routers yet - the unified gatePublish gate does that in a later phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * f…
1 parent 050aa81 commit 4678f5a

418 files changed

Lines changed: 131774 additions & 24954 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ yarn-error.log*
3030
# local env files
3131
.env*
3232
!.env.example
33+
env.aws
3334

3435
# vercel
3536
.vercel
@@ -71,3 +72,7 @@ ssmSetup.zsh
7172

7273
# Snyk Security Extension - AI Rules (auto-generated)
7374
.github/instructions/snyk_rules.instructions.md
75+
76+
# Local-only deploy notes (never commit)
77+
local.md
78+
logs/

README.md

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
![Codu Logo](https://raw.githubusercontent.com/codu-code/codu/develop/public/images/codu-gradient.png)
66

7-
> A space for coders
7+
> A community for AI builders and indie hackers
88
9-
Codú is the ultimate community of web developers to learn, share, and get support for your projects, either big or small. It is the perfect place to sharpen your skills and build your portfolio. In Codú, we're all here to help each other to grow as web developers. Plus, Codú makes it easier to find collaborators for your next big project.
9+
Codú is a community for AI builders and indie hackers. Share what you're building, learn from people shipping real projects, and get support — whether it's a weekend experiment or a product you're taking to market. Write articles, post TILs, start discussions, ask questions, and find people to build with.
1010

1111
---
1212

@@ -53,6 +53,14 @@ npm run db:migrate
5353

5454
The full command can be seen in our [package.json](/package.json#16) file.
5555

56+
> Deploy note: the Vercel Build Command runs `npm run db:migrate && npm run ci-build`
57+
> on **every** target, so **both preview and production builds migrate** — preview
58+
> against the shared dev database, production against prod. A migration that fails
59+
> aborts the build, so a branch carrying a broken or environment-incompatible
60+
> migration turns the Vercel check red. Connections require SSL
61+
> (`sslmode=require` in `DATABASE_URL`) because the RDS instances run with
62+
> `rds.force_ssl` on; local dev and e2e use Docker over localhost and need none.
63+
5664
7. Seed the database with some mock data by running:
5765

5866
```bash
@@ -69,17 +77,15 @@ npm run dev
6977

7078
After completion of the above commands, navigate to [http://localhost:3000](http://localhost:3000) in your browser to see the result.
7179

72-
You can start your journey by modifying `pages/index.tsx`. With the auto-update feature, pages update as you edit the file.
73-
74-
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
80+
The app uses the Next.js App Router — routes live in the `app/` directory and hot-reload as you edit. The home feed is served from `app/(app)/page.tsx`.
7581

76-
Learn more about API routes [here](https://nextjs.org/docs/api-routes/introduction).
82+
API endpoints are [Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) under `app/api/*`, alongside the tRPC routers in `server/`.
7783

7884
## Environment Variables
7985

8086
### DATABASE_URL
8187

82-
The `DATABASE_URL` is a connection string to a PostgreSQL database (version 15.0).
88+
The `DATABASE_URL` is a connection string to a PostgreSQL database (version 15).
8389

8490
By default, we point to a database running locally with Docker from our `docker-compose.yml` file.
8591

@@ -89,6 +95,12 @@ Run the command `docker compose up`.
8995

9096
Alternatively, if you have PostgreSQL running locally, you can use your local connection string or grab one from a free service like [Supabase](https://supabase.com/docs/guides/database/connecting-to-postgres#finding-your-connection-string).
9197

98+
### Local email (Mailpit)
99+
100+
`docker compose up` also starts [Mailpit](https://mailpit.axllent.org/), a local email catcher. Set `EMAIL_PROVIDER=local` in your `.env` and every outgoing email (magic-link sign-in, moderation/report notifications) is captured at [http://localhost:8027](http://localhost:8027) instead of being sent through SES — no AWS credentials or real inboxes needed in development.
101+
102+
The email E2E spec (`e2e/email.spec.ts`) asserts delivery through Mailpit's API; it skips automatically when Mailpit isn't running. To include it: `EMAIL_PROVIDER=local npm run dev:e2e` (server) and `EMAIL_PROVIDER=local npm test` (tests).
103+
92104
### GITHUB_ID and GITHUB_SECRET
93105

94106
Currently, we only allow authentication via GitHub. To enable this, you need to have a `GITHUB_ID` and `GITHUB_SECRET` value.

app/(app)/(tsandcs)/layout.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ export default function RootLayout({
44
children: React.ReactNode;
55
}) {
66
return (
7-
<div className="prose mx-auto max-w-3xl dark:prose-invert lg:prose-lg">
8-
{children}
7+
<div className="bg-canvas text-fg">
8+
<div className="prose mx-auto max-w-prose px-5 py-16 sm:px-8 sm:py-20">
9+
{children}
10+
</div>
911
</div>
1012
);
1113
}

0 commit comments

Comments
 (0)