Design changes #48
Conversation
Expanded from ~184 lines to 875 lines across 25 numbered sections.
Fixes:
- Routing: src/middleware.ts -> src/proxy.ts (Next 16 rename); / is a
redirector via HomeClient, not the profile dashboard; canonical profile
URL is /profile/[did].
- Layout: provider tree now includes Providers passthrough, skip-nav
anchor, and <Analytics /> mount.
- Security: blob limits corrected (4 MB XRPC proxy, 5 MB group route);
added safeExternalUrl, safeRedirect, postMessage origin checks,
repo-ownership enforcement, allowed write collections enumerated,
HMAC timingSafeEqual cookie shape, global HTTP headers from
next.config.ts.
- File map: removed phantom sidebar.tsx; added profile/[did]/, all
group API subroutes, lib/utils/{url,api,config,sanitize,constants}.ts,
lib/groups/{proxy-agent,index,use-org-limit}.ts, lib/atproto/types.ts,
component dir enumerations, tests/groups.test-plan.md, public/brand/.
- Auth: cookie shape, Redis key prefixes, OAuth scope, confidential vs
public client mode, session-fixation rationale.
Adds new sections: tech stack, environment variables, API routes
catalog, hooks catalog, groups feature deep dive, identity-link /
EIP-712 flow, common pitfalls, feature/route checklists.
tsc --noEmit passes.
…rand Shifted /welcome from pure-grayscale editorial to a brand-anchored layout that carries a single Certified-blue accent (#1d4ed8 — chosen to be distinct from Bluesky's electric #0085FF) through every section. Hero - Replace bare uppercase 'BUILT ON AT PROTOCOL' label with a brand-soft pill chip (anchor link to #built-for-trust) - Add radial blue glow behind the headline - Rewrite subtitle to actually describe the product - Add 'Free / No passwords / No vendor lock-in' trust microcopy under the CTA so the most common objection is pre-empted What you get (bento) - First card switched to brand-fill highlight variant — the eye now has a clear entry point into the grid - All four cards: 48px tinted icon circles with hover scale - Body copy made more concrete How it works - Italic numerals (01 02 03) replaced with circled brand-blue rings - Vertical gradient connector line ties the steps together visually - Hover fills the ring Partner apps - Removed the greyscale filter and faded-name styling that made the section read as placeholder content - Logos always full-color, in proper rounded white cards - Brand-blue ring on hover Closing CTA - New .landing-section--brand variant with radial-into-linear gradient - Distinct closing moment instead of literal hero repeat - Trust microcopy reinforced in brand surface Footer - Single-row replaced with multi-column (Product / Company / Legal) - Added 'Powered by AT Protocol' pill linking to atproto.com - Stacks to one column at 768px, then collapses Legal at 480px Tokens - 6 new --color-brand-* tokens; existing grayscale palette untouched - All changes scoped to welcome page; About/Terms/Privacy still wear the older navy palette and are flagged as follow-up in pi-eval audit Verified: build + lint clean, mobile (390x844) audited, a11y tree preserved (heading hierarchy linear, focus traps intact, all interactive elements keep their accessible name). Audit report: pi-eval/reports/certified-app/bluesky-comparison-redesign/
Replaces v1's Bluesky-inspired blue with a warm parchment + sienna system aligned with MaEarth (a partner app already shipping the Certified sign-in widget). Sampled MaEarth's live page colors directly to rebuild the Certified palette from real numbers, not guesses: --color-surface #f7f1eb (cream parchment, was off-white) --color-primary #141413 (warm near-black, was #111) --color-mid-gray #726a60 (warm body gray, was cool #7e7576) --color-light-gray #e8ddd1 (warm hairline) --color-brand #c68b5a (sienna — replaces #1d4ed8 blue) Removed (v1 → v2): - Radial blue glow behind hero (now displays nothing — type carries it) - Brand-fill highlight bento card (all four cards uniform with hairline tops) - Gradient connector line in How it works (now hairline-divided rows) - Brand-blue ring on partner card hover (now soft cream tint in cell) - Bold blue gradient closing CTA (now quiet cream with hairline top) - Colored 'Powered by AT Protocol' footer pill (now hairline cream) - Tinted icon circles on bento cards (now monochrome stroke icons) Other refinements: - Bento icons turn sienna only on hover — no other accent - How-it-works numerals back to italic editorial figures (Instrument Serif), stained sienna on hover only - Partner cells become a hairline-divided table (top + bottom rule across the grid, vertical rules between cells) - Built-for-trust dark section warmed from #111 to #1f1c19 so it harmonizes with the cream rest of the page - Eyebrow labels back to mid-gray (the accent is now reserved for hover affordances and the chip dot) - Body bg flash color changed from --color-primary to --color-surface so there's no dark flash during auth load Bonus fix: FAQ focus-ring overlap from v1's audit closed in same pass via outline-offset: 2px → -2px. Audit report: pi-eval/reports/certified-app/maearth-warm-minimal-redesign/ Previous (rejected): pi-eval/reports/certified-app/bluesky-comparison-redesign/
Rebuild the landing hero and supporting pages around an editorial print-and-vermillion design language drawn from a reference comp. Hero is now a 12-col split: a typography column on the left (vertical 'Your identity, your choice.' rail + red chop seal stamp + 'THE UNIVERSAL IDENTITY' eyebrow + 'Portable identity for the *atmosphere*.' headline with italic-red emphasis + dual buttons + '@ Built on AT Protocol. Learn more →' footnote) and a hand-built identity diagram on the right (centre card with globe/handle/DID/4 chip pills/lock; six partner-app cards floating left & right; dotted connectors with + corner joints; watercolor blue blob; 'Your open identity' top tag; bottom caption; AT/PROTOCOL red badge; vertical 'OPEN WEB' rail with red dot). A pillar strip (01 Protect / 02 Connect / 03 Can choose) sits below the hero with circle-outline icons, italic-red Instrument Serif numerals, serif title, and sans body. Also: - Brand token retuned from sienna #c68b5a → editorial vermillion #c2392c. Cream surface tweaked toward parchment. - New shared .btn / .btn--primary / --secondary / --ghost / --brand / --sm / --lg / --block system used by hero CTAs, navbar 'Get started', and the closing 'Ready when *you* are.' section (which also picks up the italic-red emphasis pattern). - Navbar gains a centred anchor nav (Benefits / How it works / Apps / FAQ) for unauth state, plus 'Sign in' text link + 'Get started' black CTA. Mobile-tightened (drops the bare link + BETA label ≤520px, cuts side padding ≤380px). - New .prose-page editorial wrapper for /about, /privacy, /terms, /dsa with serif headline, italic-red emphasis, hairline section rules, brand-coloured links, and uniform vertical rhythm. - Tailwind theme aligned to the new palette; new font-family aliases for serif and serif-italic. - Mobile diagram simplified: partner cards + connector lines hidden, central card shown at full width. - New components: chop-seal.tsx, hero-diagram.tsx, hero-actions.tsx. Old hero-signin-button.tsx removed. Verified visually against the reference at desktop (1280) and mobile (390) viewports via agent-browser. `npm run build` and `npx tsc --noEmit` pass clean; `npm run lint` shows the same 9 pre-existing errors in untouched files (no new errors).
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR overhauls the public site/landing UI and design system (colors, fonts, hero/footer/navbar, landing sections, buttons, prose layout) and adds new landing components ( ChangesLanding & Public Site Redesign
Developer Documentation (separate DAG)
Misc / Config
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
After a closer look at the reference comp, the surface is much closer to paper-white than the cream I had — and the red is a touch warmer and brighter. Re-tune both, lift card surfaces to pure #fff so they read as subtle elevation against the off-white page, and unify section backgrounds (subtle/cream now share the page surface and rely on a hairline border for separation, not a tonal step). Watercolor blue circle behind the identity card is still SVG and will be replaced with a real watercolor PNG (openai_image, pending auth) in a follow-up. Tokens: - --color-surface #f6f1e8 → #faf9f5 - --color-off-white #f6f1e8 → #faf9f5 - --color-gray-100 #ece5d8 → #f1efe8 - --color-light-gray #e1d8c8 → #e4e0d4 - --color-mid-gray #6a6258 → #6e665c - --color-surface-container #ece5d8 → #f1efe8 - --color-surface-container-low #fcfaf6 → #fdfdf9 - --color-surface-container-high #e6dccb → #ebe7da - --color-brand #c2392c → #d04636 - --color-brand-hover #a82e22 → #b3382a - (brand-soft / -tint / -text adjusted to match) Cards (top tag, identity card, partner-app cards, AT badge) now use pure #ffffff against the off-white page surface.
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
src/components/landing/landing-page.tsx (1)
68-74: 💤 Low valueConsider using
Linkfor the in-page anchor for consistency.The footnote link uses
<a href="#built-for-trust">whileready-cta-button.tsxuses<Link href="#partner-apps">for similar in-page navigation. UsingLinkconsistently would align the codebase patterns.♻️ Optional consistency fix
+import Link from "next/link"; + - <a className="hero__footnote-link" href="#built-for-trust"> + <Link className="hero__footnote-link" href="#built-for-trust"> Learn more <span aria-hidden="true">→</span> - </a> + </Link>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/landing/landing-page.tsx` around lines 68 - 74, Replace the plain anchor used in the LandingPage footnote with the project's Link component: change the <a href="#built-for-trust" className="hero__footnote-link"> element inside the hero__footnote paragraph to use <Link href="#built-for-trust"> (keeping the inner text "Learn more" and the trailing span aria-hidden="true">→</span> and preserving the className on either the Link or its child as your Link API requires), and add the appropriate import for Link (e.g., import Link from 'next/link' or the local Link wrapper) at the top of src/components/landing/landing-page.tsx so in-page navigation is consistent with ready-cta-button.tsx.src/components/landing/sections/what-you-get.tsx (1)
14-39: 💤 Low valueConsider using
lucide-reacticons for consistency.The inline SVGs (Users, User, Lock, Mail icons) have direct equivalents in
lucide-reactwhich is already used elsewhere in the codebase. This would improve maintainability and consistency.♻️ Suggested refactor using lucide-react
+import { Users, User, Lock, Mail } from "lucide-react"; + export default function WhatYouGet() { return ( // ... <div className="landing-bento__card"> <div className="landing-bento__icon-wrap"> - <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg> + <Users size={24} strokeWidth={1.75} aria-hidden="true" /> </div> // ... repeat pattern for User, Lock, MailAs per coding guidelines: "Use
lucide-reactfor new icon implementations."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/landing/sections/what-you-get.tsx` around lines 14 - 39, Replace the inline SVGs inside the landing-bento__card blocks with lucide-react icon components to match the codebase convention: import Users, User, Lock, Mail from 'lucide-react' at the top of the WhatYouGet component, then swap each <svg> block for the corresponding component (e.g., <Users />, <User />, <Lock />, <Mail />), preserving attributes like width/height, stroke/currentColor styling, and aria-hidden; ensure any surrounding markup (the div with class landing-bento__icon-wrap and the card headings/p paragraphs) remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@AGENTS.md`:
- Around line 656-670: Update the landing component map in AGENTS.md to reflect
the new hero implementation: remove or replace the stale reference to
src/components/landing/hero-signin-button.tsx and add entries for
src/components/landing/hero-actions.tsx,
src/components/landing/hero-diagram.tsx, and
src/components/landing/chop-seal.tsx (keeping the existing sections/ list intact
such as faq-content.tsx, ready-cta-button.tsx, etc.); ensure the descriptive
comments match the new files' responsibilities (e.g., hero assembly, hero
actions CTA, hero diagram) so contributors are directed to the correct source
files.
- Around line 106-127: The fenced code blocks in the AGENTS.md diff lack a
language tag and contain unescaped pipe characters that break the Markdown table
rendering; fix by adding a language identifier to each fenced block (e.g.,
```text) and escape any literal pipe characters inside the blob MIME list and
group-table rows (replace | with \| or wrap the MIME entries in inline
code/backticks) so the groups table rows render correctly; apply the same
changes to the other affected blocks mentioned (the blocks around the blob MIME
list and any multi-line ASCII diagrams) so markdownlint no longer flags missing
languages or unescaped pipes.
- Around line 522-527: The global header in next.config.ts sets X-Frame-Options:
DENY for source "/(.*)" which blocks the same-origin iframe OAuth callback;
update next.config.ts to add a route-level exception for "/oauth/callback" (or
its exact path pattern) that sets X-Frame-Options: SAMEORIGIN so the iframe can
load and allow the iframe detection logic (window.parent !== window) and
postMessage to run, or alternatively update AGENTS.md to explicitly state the
iframe callback flow is non-functional if you prefer not to change headers.
In `@src/app/dsa/page.tsx`:
- Around line 188-192: The Terms of Service anchor should use Next's client-side
router: import Link from 'next/link' in the component file (page.tsx) and
replace the raw <a href="/terms"> element with a Next Link element (e.g., <Link
href="/terms">Terms of Service</Link>), preserving any surrounding text or
props; ensure you remove the plain <a> import/usage and keep SSR/SEO behavior
intact.
In `@src/app/terms/page.tsx`:
- Around line 303-306: Replace the raw anchor <a href="/dsa">DSA Compliance
Page</a> with Next.js Link usage: import Link from 'next/link' (if not already
imported) and change the JSX to use <Link href="/dsa">DSA Compliance Page</Link>
so internal routing uses next/link; ensure you remove or keep no duplicate
anchor tags and preserve surrounding punctuation (the trailing period).
In `@src/components/landing/chop-seal.tsx`:
- Around line 12-19: The SVG is decorative but currently exposes itself to
assistive tech via role="img" and aria-label; update the SVG element in the
ChopSeal component (the <svg ... className="chop-seal"> element) to be hidden
from AT by removing role="img" and aria-label and adding aria-hidden="true" (and
optionally focusable="false" or role="presentation" if your linter requires) so
screen readers ignore this ornament.
In `@src/components/layout/navbar.tsx`:
- Around line 406-413: The editorial anchor nav (the div with className
"navbar__public-links" containing Link to "/welcome#what-you-get",
"/welcome#how-it-works", "/welcome#partner-apps", "/welcome#faq") currently
renders on every unauthenticated route; restrict it to only render when the user
is on the landing page by checking the router path inside the Navbar component
(use useRouter().pathname or usePathname() depending on the Next.js version) and
render the div only when pathname === "/welcome" (or, if you prefer global nav
behavior, rename/replace the hrefs with site-wide routes without fragment
behavior). Ensure the conditional guards the whole block so those Link elements
are not shown on /about, /privacy, etc.
---
Nitpick comments:
In `@src/components/landing/landing-page.tsx`:
- Around line 68-74: Replace the plain anchor used in the LandingPage footnote
with the project's Link component: change the <a href="#built-for-trust"
className="hero__footnote-link"> element inside the hero__footnote paragraph to
use <Link href="#built-for-trust"> (keeping the inner text "Learn more" and the
trailing span aria-hidden="true">→</span> and preserving the className on either
the Link or its child as your Link API requires), and add the appropriate import
for Link (e.g., import Link from 'next/link' or the local Link wrapper) at the
top of src/components/landing/landing-page.tsx so in-page navigation is
consistent with ready-cta-button.tsx.
In `@src/components/landing/sections/what-you-get.tsx`:
- Around line 14-39: Replace the inline SVGs inside the landing-bento__card
blocks with lucide-react icon components to match the codebase convention:
import Users, User, Lock, Mail from 'lucide-react' at the top of the WhatYouGet
component, then swap each <svg> block for the corresponding component (e.g.,
<Users />, <User />, <Lock />, <Mail />), preserving attributes like
width/height, stroke/currentColor styling, and aria-hidden; ensure any
surrounding markup (the div with class landing-bento__icon-wrap and the card
headings/p paragraphs) remains unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 261c452d-93dd-4a61-a698-bc925b96b295
📒 Files selected for processing (17)
AGENTS.mdsrc/app/about/page.tsxsrc/app/dsa/page.tsxsrc/app/globals.csssrc/app/privacy/page.tsxsrc/app/terms/page.tsxsrc/components/landing/chop-seal.tsxsrc/components/landing/hero-actions.tsxsrc/components/landing/hero-diagram.tsxsrc/components/landing/hero-signin-button.tsxsrc/components/landing/landing-page.tsxsrc/components/landing/sections/ready-cta-button.tsxsrc/components/landing/sections/ready-cta-content.tsxsrc/components/landing/sections/what-you-get.tsxsrc/components/layout/footer.tsxsrc/components/layout/navbar.tsxtailwind.config.ts
💤 Files with no reviewable changes (1)
- src/components/landing/hero-signin-button.tsx
| `next.config.ts` sets these on every response: | ||
|
|
||
| - `X-Content-Type-Options: nosniff` | ||
| - `Referrer-Policy: strict-origin-when-cross-origin` | ||
| - `X-Frame-Options: DENY` (the OAuth callback iframe is same-origin, so this is fine) | ||
| - `Permissions-Policy: camera=(), microphone=(), geolocation=()` |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== next.config header configuration =="
rg -n -C3 'X-Frame-Options|headers\s*\(' next.config.ts
echo
echo "== OAuth callback page =="
sed -n '1,220p' src/app/oauth/callback/page.tsx
echo
echo "== iframe/postMessage flow references =="
rg -n -C2 'oauth-callback-complete|postMessage|iframe' \
src/lib/auth/auth-context.tsx \
src/components/ui/sign-in-modal.tsxRepository: hypercerts-org/certified-app
Length of output: 3696
X-Frame-Options: DENY globally prevents the iframe callback flow from functioning.
The next.config.ts header applies to all routes via source: "/(.*)", blocking all same-origin framing. However, /oauth/callback contains iframe detection logic (window.parent !== window) and postMessage communication that cannot execute because the browser blocks iframe loads at the HTTP level before any JavaScript runs. Either /oauth/callback needs a route-level exception to use X-Frame-Options: SAMEORIGIN, or the documentation should clarify that the iframe flow is not currently functional.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@AGENTS.md` around lines 522 - 527, The global header in next.config.ts sets
X-Frame-Options: DENY for source "/(.*)" which blocks the same-origin iframe
OAuth callback; update next.config.ts to add a route-level exception for
"/oauth/callback" (or its exact path pattern) that sets X-Frame-Options:
SAMEORIGIN so the iframe can load and allow the iframe detection logic
(window.parent !== window) and postMessage to run, or alternatively update
AGENTS.md to explicitly state the iframe callback flow is non-functional if you
prefer not to change headers.
| As described in the{" "} | ||
| <a href="/terms" className="text-blue-600 underline hover:text-blue-800"> | ||
| <a href="/terms"> | ||
| Terms of Service | ||
| </a> | ||
| , the Hypercerts Foundation may restrict access to content or suspend accounts in |
There was a problem hiding this comment.
Use next/link for internal route to /terms.
The Terms of Service link uses a raw <a> tag instead of next/link for internal navigation.
🔧 Proposed fix
+import Link from "next/link";
+
// ... in the JSX:
As described in the{" "}
- <a href="/terms">
+ <Link href="/terms">
Terms of Service
- </a>
+ </Link>As per coding guidelines: "Use next/link for internal navigation; never use <a href> for in-app routes."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| As described in the{" "} | |
| <a href="/terms" className="text-blue-600 underline hover:text-blue-800"> | |
| <a href="/terms"> | |
| Terms of Service | |
| </a> | |
| , the Hypercerts Foundation may restrict access to content or suspend accounts in | |
| As described in the{" "} | |
| <Link href="/terms"> | |
| Terms of Service | |
| </Link> | |
| , the Hypercerts Foundation may restrict access to content or suspend accounts in |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/dsa/page.tsx` around lines 188 - 192, The Terms of Service anchor
should use Next's client-side router: import Link from 'next/link' in the
component file (page.tsx) and replace the raw <a href="/terms"> element with a
Next Link element (e.g., <Link href="/terms">Terms of Service</Link>),
preserving any surrounding text or props; ensure you remove the plain <a>
import/usage and keep SSR/SEO behavior intact.
| <a href="/dsa"> | ||
| DSA Compliance Page | ||
| </a> | ||
| . |
There was a problem hiding this comment.
Use next/link for internal route to /dsa.
The DSA Compliance Page link uses a raw <a> tag instead of next/link for internal navigation.
🔧 Proposed fix
+import Link from "next/link";
+
// ... in the JSX:
- <a href="/dsa">
+ <Link href="/dsa">
DSA Compliance Page
- </a>
+ </Link>As per coding guidelines: "Use next/link for internal navigation; never use <a href> for in-app routes."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/terms/page.tsx` around lines 303 - 306, Replace the raw anchor <a
href="/dsa">DSA Compliance Page</a> with Next.js Link usage: import Link from
'next/link' (if not already imported) and change the JSX to use <Link
href="/dsa">DSA Compliance Page</Link> so internal routing uses next/link;
ensure you remove or keep no duplicate anchor tags and preserve surrounding
punctuation (the trailing period).
| <> | ||
| {/* Center: editorial anchor nav (only on landing) */} | ||
| <div className="navbar__public-links" aria-label="Sections"> | ||
| <Link href="/welcome#what-you-get" className="navbar__public-link">Benefits</Link> | ||
| <Link href="/welcome#how-it-works" className="navbar__public-link">How it works</Link> | ||
| <Link href="/welcome#partner-apps" className="navbar__public-link">Apps</Link> | ||
| <Link href="/welcome#faq" className="navbar__public-link">FAQ</Link> | ||
| </div> |
There was a problem hiding this comment.
Scope the section anchors to /welcome or rename them as global nav.
The inline comment says this block is "only on landing", but it renders on every unauthenticated route. On /about, /privacy, etc. these links bounce users back to /welcome#…, which is a pretty different experience from in-page section navigation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/layout/navbar.tsx` around lines 406 - 413, The editorial
anchor nav (the div with className "navbar__public-links" containing Link to
"/welcome#what-you-get", "/welcome#how-it-works", "/welcome#partner-apps",
"/welcome#faq") currently renders on every unauthenticated route; restrict it to
only render when the user is on the landing page by checking the router path
inside the Navbar component (use useRouter().pathname or usePathname() depending
on the Next.js version) and render the div only when pathname === "/welcome"
(or, if you prefer global nav behavior, rename/replace the hrefs with site-wide
routes without fragment behavior). Ensure the conditional guards the whole block
so those Link elements are not shown on /about, /privacy, etc.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/app/globals.css (2)
2330-2340: ⚡ Quick winRemove duplicated mobile media rule for
.landing-network.The second
@media (max-width: 768px)block repeats the samegrid-template-columnsassignment and can drift over time. Keep a single block.Proposed cleanup
`@media` (max-width: 768px) { .landing-network { grid-template-columns: repeat(2, 1fr); } .landing-network__cell:nth-child(2n) { border-right: none; } .landing-network__cell:nth-child(-n+2) { border-bottom: 1px solid var(--color-light-gray); } } - -@media (max-width: 768px) { - .landing-network { - grid-template-columns: repeat(2, 1fr); - } -}Also applies to: 2342-2346
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/globals.css` around lines 2330 - 2340, There are duplicate `@media` (max-width: 768px) blocks setting .landing-network rules; remove the redundant media block and consolidate the mobile styles into a single `@media` (max-width: 768px) section so .landing-network keeps only one grid-template-columns declaration and both .landing-network__cell:nth-child(2n) { border-right: none; } and .landing-network__cell:nth-child(-n+2) { border-bottom: 1px solid var(--color-light-gray); } remain under that single media rule.
665-670: ⚡ Quick winSimplify hero CTA spacing to avoid wrap edge-cases.
Line 665-670 sets
gap: 0and then spacing is reintroduced via sibling margins at Line 1346-1350. This is fragile when buttons wrap at intermediate widths. Prefer usinggapconsistently.Proposed simplification
.hero__actions { display: inline-flex; align-items: stretch; - gap: 0; + gap: 12px; flex-wrap: wrap; margin-bottom: 28px; } -/* Spacing tweak when btn pair sits on the hero */ -.hero__actions .btn + .btn { margin-left: 12px; } `@media` (max-width: 480px) { .hero__actions { flex-direction: column; align-items: stretch; } - .hero__actions .btn + .btn { margin-left: 0; margin-top: 12px; } }Also applies to: 1346-1350
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/globals.css` around lines 665 - 670, The hero CTA container currently sets gap: 0 and relies on per-button sibling margins elsewhere which breaks when buttons wrap; update the container rule that contains display: inline-flex; align-items: stretch; flex-wrap: wrap; (the hero CTA container block shown with gap: 0) to use a consistent gap value (e.g., gap: 12px) and remove the sibling margin rules applied to individual buttons (the rules at lines around 1346-1350 that add left/right margins to CTA buttons), so spacing is handled by gap and wrapping behaves predictably.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/globals.css`:
- Line 943: Replace hard-coded color literals used as background values with CSS
custom properties defined on :root; create appropriately named tokens (e.g.
--color-background, --color-surface, --color-overlay) in the :root rule and then
update every occurrence of raw hex/rgba (the background: `#ffffff`; and the other
similar background declarations) to use var(--color-*) instead; ensure token
names follow existing conventions like
--color-primary/--color-navy/--border-default and update any transition literals
to use --transition-base if applicable so all palette and transition values are
centralized.
- Line 2536: Replace the hard-coded "transition: border-color 300ms ease-out;"
with the theme transition token by using the CSS variable (e.g. change to use
var(--transition-base)); locate the rule containing the transition property and
update it so it reads "transition: border-color var(--transition-base);" to
ensure consistency with other tokens like --transition-base and avoid hard-coded
timings.
---
Nitpick comments:
In `@src/app/globals.css`:
- Around line 2330-2340: There are duplicate `@media` (max-width: 768px) blocks
setting .landing-network rules; remove the redundant media block and consolidate
the mobile styles into a single `@media` (max-width: 768px) section so
.landing-network keeps only one grid-template-columns declaration and both
.landing-network__cell:nth-child(2n) { border-right: none; } and
.landing-network__cell:nth-child(-n+2) { border-bottom: 1px solid
var(--color-light-gray); } remain under that single media rule.
- Around line 665-670: The hero CTA container currently sets gap: 0 and relies
on per-button sibling margins elsewhere which breaks when buttons wrap; update
the container rule that contains display: inline-flex; align-items: stretch;
flex-wrap: wrap; (the hero CTA container block shown with gap: 0) to use a
consistent gap value (e.g., gap: 12px) and remove the sibling margin rules
applied to individual buttons (the rules at lines around 1346-1350 that add
left/right margins to CTA buttons), so spacing is handled by gap and wrapping
behaves predictably.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
| border: 0; | ||
| border-top: 1px solid var(--color-light-gray); | ||
| border-radius: 0; | ||
| transition: border-color 300ms ease-out; |
There was a problem hiding this comment.
Use transition token for new timing value.
Line 2536 hard-codes 300ms ease-out; this should use an existing transition variable for consistency with the rest of the theme system.
Proposed fix
.landing-bento__card {
- transition: border-color 300ms ease-out;
+ transition: border-color var(--transition-base);
}As per coding guidelines "**/*.css: Reuse CSS variables (--color-primary, --color-navy, --border-default, --transition-base, etc.) from :root; do not hard-code colors or transitions".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| transition: border-color 300ms ease-out; | |
| .landing-bento__card { | |
| transition: border-color var(--transition-base); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/globals.css` at line 2536, Replace the hard-coded "transition:
border-color 300ms ease-out;" with the theme transition token by using the CSS
variable (e.g. change to use var(--transition-base)); locate the rule containing
the transition property and update it so it reads "transition: border-color
var(--transition-base);" to ensure consistency with other tokens like
--transition-base and avoid hard-coded timings.
The reference comp clearly shows a hand-painted watercolor circle behind the central identity card — soft feathered edges, density variations from pigment pooling, paper grain — none of which a flat SVG ellipse + radial gradient can credibly reproduce. Generate the watercolor in 1024² via openai_image (dusty cornflower blue between #b0c4de and #c5d5e8, ensō-style imperfect circle), save to public/assets/watercolor-blue-circle.png, and replace the SVG blob in hero-diagram.tsx with an <img>. CSS: mix-blend-mode: multiply collapses the white paper background in the PNG into the off-white page surface so only the pigment is visible. opacity 0.55 keeps it as a quiet wash so it sits behind the central card rather than competing with it. Also add .pi/ to .gitignore so locally-cached generation byproducts (failed prompts that came back as 'green light' before the user pasted the actual prompt) don't leak into the repo.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/globals.css`:
- Around line 1010-1023: Replace the remaining hard-coded colors and shadows in
the new hero-diagram styles with theme variables: update the box-shadow
declaration (the rule above .hero-diagram__card-globe) to use a shadow variable
(e.g., var(--elevation-2-shadow) and var(--shadow-inset) or add those to :root
if they don’t exist) instead of rgba(20,20,19,0.05) and rgba(255,255,255,0.5),
and change the hard-coded foreground color `#4a6fa5` inside
.hero-diagram__card-globe to var(--color-navy) or var(--color-primary); likewise
replace any other hard-coded palette values in the hero diagram block (the
styles around lines 1080–1095) with existing variables such as --border-default,
--color-surface-container-low, --color-navy/--color-primary, or add the
appropriate variables to :root before using them.
- Around line 2095-2099: The CSS rule .prose-page__body a uses the keyword
"currentColor" which violates stylelint's value-keyword-case; update the
border-bottom declaration in the .prose-page__body a rule to use the lowercase
keyword "currentcolor" (i.e., change the value in the border-bottom: 1px solid
currentColor to currentcolor) so the linter's value-keyword-case rule passes.
- Around line 809-813: The hero animation delay rules currently target
.hero__inner > .hero-reveal:nth-child(n) but the redesigned landing page uses
.hero__split, so update the selectors to match the new markup: either replace
the existing selectors with .hero__split > .hero-reveal:nth-child(1/2/3/4/5) or
add equivalent rules alongside the current ones so both .hero__inner and
.hero__split are covered; adjust the CSS where .hero__inner >
.hero-reveal:nth-child(...) appears (the animation-delay rules) and keep or
update the nearby comment explaining that sub-pages use .hero__inner while the
landing page uses .hero__split.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c0a545bb-db75-4177-85e5-c5522b06e180
⛔ Files ignored due to path filters (1)
public/assets/watercolor-blue-circle.pngis excluded by!**/*.png
📒 Files selected for processing (3)
.gitignoresrc/app/globals.csssrc/components/landing/hero-diagram.tsx
✅ Files skipped from review due to trivial changes (1)
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/landing/hero-diagram.tsx
| .hero__inner > .hero-reveal:nth-child(1) { animation-delay: 0ms; } | ||
| .hero__inner > .hero-reveal:nth-child(2) { animation-delay: 100ms; } | ||
| .hero__inner > .hero-reveal:nth-child(3) { animation-delay: 200ms; } | ||
| .hero__inner > .hero-reveal:nth-child(2) { animation-delay: 80ms; } | ||
| .hero__inner > .hero-reveal:nth-child(3) { animation-delay: 160ms; } | ||
| .hero__inner > .hero-reveal:nth-child(4) { animation-delay: 240ms; } | ||
| .hero__inner > .hero-reveal:nth-child(5) { animation-delay: 320ms; } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C3 --glob '*.tsx' 'hero__split|hero__inner|hero-reveal' src/components/landing src/appRepository: hypercerts-org/certified-app
Length of output: 584
🏁 Script executed:
rg -n -A 20 'hero__split' src/components/landing/landing-page.tsx | head -60Repository: hypercerts-org/certified-app
Length of output: 1029
🏁 Script executed:
rg -n -B 5 -A 3 'hero-reveal' src/components/landing/landing-page.tsxRepository: hypercerts-org/certified-app
Length of output: 54
🏁 Script executed:
rg -n 'hero-reveal|hero__inner' src/Repository: hypercerts-org/certified-app
Length of output: 1313
Update the hero stagger animation selectors for the redesigned landing page.
Lines 809-813 target .hero__inner > .hero-reveal:nth-child(n), but the redesigned landing page now uses .hero__split instead of .hero__inner. These animation delays will not apply to the new hero markup—the selectors no longer match any elements in the landing page.
The file comment at line 469 already documents this split ("sub-page uses .hero__inner. The landing page uses .hero__split."), but the animation rules were not updated to reflect the new structure. Either update these selectors to target the actual markup hierarchy of the redesigned hero, or clarify that these rules apply only to sub-pages.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/globals.css` around lines 809 - 813, The hero animation delay rules
currently target .hero__inner > .hero-reveal:nth-child(n) but the redesigned
landing page uses .hero__split, so update the selectors to match the new markup:
either replace the existing selectors with .hero__split >
.hero-reveal:nth-child(1/2/3/4/5) or add equivalent rules alongside the current
ones so both .hero__inner and .hero__split are covered; adjust the CSS where
.hero__inner > .hero-reveal:nth-child(...) appears (the animation-delay rules)
and keep or update the nearby comment explaining that sub-pages use .hero__inner
while the landing page uses .hero__split.
| .prose-page__body a { | ||
| color: var(--color-brand); | ||
| text-decoration: none; | ||
| border-bottom: 1px solid currentColor; | ||
| transition: color var(--transition-fast), border-color var(--transition-fast); |
There was a problem hiding this comment.
Fix the Stylelint failure on currentcolor.
Line 2098 currently violates value-keyword-case, so this will keep lint noisy until the keyword is lowercased.
Proposed fix
.prose-page__body a {
color: var(--color-brand);
text-decoration: none;
- border-bottom: 1px solid currentColor;
+ border-bottom: 1px solid currentcolor;
transition: color var(--transition-fast), border-color var(--transition-fast);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .prose-page__body a { | |
| color: var(--color-brand); | |
| text-decoration: none; | |
| border-bottom: 1px solid currentColor; | |
| transition: color var(--transition-fast), border-color var(--transition-fast); | |
| .prose-page__body a { | |
| color: var(--color-brand); | |
| text-decoration: none; | |
| border-bottom: 1px solid currentcolor; | |
| transition: color var(--transition-fast), border-color var(--transition-fast); | |
| } |
🧰 Tools
🪛 Stylelint (17.10.0)
[error] 2098-2098: Expected "currentColor" to be "currentcolor" (value-keyword-case)
(value-keyword-case)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/app/globals.css` around lines 2095 - 2099, The CSS rule .prose-page__body
a uses the keyword "currentColor" which violates stylelint's value-keyword-case;
update the border-bottom declaration in the .prose-page__body a rule to use the
lowercase keyword "currentcolor" (i.e., change the value in the border-bottom:
1px solid currentColor to currentcolor) so the linter's value-keyword-case rule
passes.
- Switch type system to IBM Plex Sans + Plex Mono (was Inter/Noto/Instrument). CSS variable names kept (--font-inter, --font-headline, --font-serif-alt) for compat with existing BEM rules in globals.css. - Drop the AT/Protocol corner badge from the hero diagram. - Drop the decorative chop-seal stamp from the left rail and remove the no-longer-referenced ChopSeal component + .chop-seal CSS. - Replace 'open web' jargon with plain copy aimed at non-technical users: right rail eyebrow ONE ACCOUNT, italic 'Sign in anywhere.'; diagram caption 'In every app you use.'; Connect pillar copy reworded. - Rotate the right rail's eyebrow + italic line so they read in the same direction as the left rail (both bottom-to-top), so the two side rails no longer look mirrored against each other. Build + tsc --noEmit clean.
Drop three elements that were either redundant or jargon-heavy:
- The 'Your open identity' top tag on the hero diagram. It was a
fourth card-area label competing with the headline, eyebrow, and
right rail — all pitching the same idea.
- The four chip pills inside the identity card (portable / open /
user-owned / interoperable). Pure feature-jargon, and the same
four ideas are restated more accessibly in the 'What you get'
section right below the hero.
- The decorative left vertical rail ('Your identity, your choice.').
It paraphrased the subtitle two inches away. Removing it lets the
headline breathe and stops the page from looking forcibly mirrored
against the right rail (which still earns its spot by labelling
the diagram).
Also cleans up the orphan CSS and the SVG connector line that fed
the now-removed top tag, plus the unused lucide-react imports
(Briefcase, Globe2, UserCircle, Repeat).
Build + tsc --noEmit clean.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/components/landing/hero-diagram.tsx (1)
15-16: ⚡ Quick winUse
next/imagefor the watercolor PNG.This is a raster, above-the-fold asset, so the plain
<img>skips Next’s optimization path and conflicts with the repo’s image guideline.Proposed patch
+import Image from "next/image"; import { Copy, Lock, Globe, Briefcase, Globe2, UserCircle, Repeat } from "lucide-react"; @@ - <img - src="/assets/watercolor-blue-circle.png" - alt="" - className="hero-diagram__blob" - aria-hidden="true" - /> + <Image + src="/assets/watercolor-blue-circle.png" + alt="" + fill + sizes="(max-width: 768px) 90vw, 420px" + className="hero-diagram__blob" + aria-hidden="true" + />As per coding guidelines "Use raw
<img>for SVG assets andnext/imagefor raster assets where optimization adds value".Also applies to: 27-32
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/landing/hero-diagram.tsx` around lines 15 - 16, The raster watercolor PNG in the HeroDiagram component is using a raw <img> and should use Next.js optimization: import Image from "next/image", replace the PNG <img> (the element rendering the watercolor background in hero-diagram.tsx) with the <Image> component, pass src to the PNG asset, include explicit width/height or use fill with parent container styling, keep the existing alt and className, and add priority for above-the-fold loading; leave SVG assets (used via <img> or inline) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/landing/hero-diagram.tsx`:
- Around line 93-96: The Copy glyph is presented as an actionable affordance but
is purely decorative; either convert it into a real copy control by replacing
the inline <Copy> with a button element that calls
navigator.clipboard.writeText(...) (or a provided copy utility), includes an
accessible aria-label (e.g., "Copy DID") and keyboard handling, or make it
non-interactive by swapping the <Copy> icon for a neutral marker and marking it
aria-hidden="true" (and remove any pointer/cursor styles). Update the JSX in the
hero-diagram component (look for the hero-diagram__card-did block and the <Copy>
usage) to implement one of these two options so semantics match behavior.
In `@src/components/landing/landing-page.tsx`:
- Around line 95-96: Update the landing copy in the LandingPage component
(src/components/landing/landing-page.tsx) to remove or correct the unsupported
"end-to-end encryption" claim: either delete the phrase "End-to-end encryption
and user-controlled data" or replace it with an accurate statement such as
"Standard security (HTTPS transport and at‑rest protections) and user-controlled
data/portability via AT Protocol"; ensure the hero pillar text
(hero-pillar__lede and its adjacent paragraph) matches this corrected wording so
the UI no longer asserts E2E encryption.
---
Nitpick comments:
In `@src/components/landing/hero-diagram.tsx`:
- Around line 15-16: The raster watercolor PNG in the HeroDiagram component is
using a raw <img> and should use Next.js optimization: import Image from
"next/image", replace the PNG <img> (the element rendering the watercolor
background in hero-diagram.tsx) with the <Image> component, pass src to the PNG
asset, include explicit width/height or use fill with parent container styling,
keep the existing alt and className, and add priority for above-the-fold
loading; leave SVG assets (used via <img> or inline) unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 17cf6e94-3d2a-4fdb-ab82-3e354cceacc5
📒 Files selected for processing (5)
src/app/globals.csssrc/app/layout.tsxsrc/components/landing/hero-diagram.tsxsrc/components/landing/landing-page.tsxtailwind.config.ts
| <p className="hero-pillar__lede">Secure by design</p> | ||
| <p>End-to-end encryption and user-controlled data.</p> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Expect architecture/docs or implementation references that explicitly back an end-to-end model.
rg -n -i -C2 'end[- ]to[- ]end|e2e|encrypt|encrypted|encryption' AGENTS.md srcRepository: hypercerts-org/certified-app
Length of output: 1121
🏁 Script executed:
# Find documentation files that might describe the architecture
find . -type f \( -name "README*" -o -name "*.md" \) | grep -E '(README|docs|arch|security|encryption)' | head -20Repository: hypercerts-org/certified-app
Length of output: 84
🏁 Script executed:
# Search for any encryption-related implementation
rg -n 'encrypt|crypto|cipher|symmetric|asymmetric|key.?exchange' --type ts --type tsx -i | head -30Repository: hypercerts-org/certified-app
Length of output: 100
🏁 Script executed:
# Check if there's any detailed description of the security model in the main README
head -100 README.md 2>/dev/null || echo "README.md not found"Repository: hypercerts-org/certified-app
Length of output: 2853
🏁 Script executed:
# Fix the rg command - search for encryption/crypto patterns
rg -n 'encrypt|crypto|cipher|seal|key' -t ts | head -40Repository: hypercerts-org/certified-app
Length of output: 3146
🏁 Script executed:
# Check privacy policy or security documentation
cat README.md | grep -i 'security\|encrypt\|privacy' || echo "No match in README"Repository: hypercerts-org/certified-app
Length of output: 104
🏁 Script executed:
# Look for any `@atproto` dependencies and crypto usage
rg -n '@atproto|atproto|crypto' -t json | head -20Repository: hypercerts-org/certified-app
Length of output: 1336
🏁 Script executed:
# Search for actual data handling in authentication/session code
find src -name '*auth*' -o -name '*session*' -o -name '*crypto*' | head -10Repository: hypercerts-org/certified-app
Length of output: 328
🏁 Script executed:
# Search for privacy policy content or any security documentation
find . -path '*/pages/*privacy*' -o -path '*/app/*privacy*' -o -name 'SECURITY*' | head -10Repository: hypercerts-org/certified-app
Length of output: 117
🏁 Script executed:
# Look at any documentation about data security or encryption in comments/docs
rg -n 'end.?to.?end|e2e|encrypted.?data|data.?encrypt' --type ts | head -20Repository: hypercerts-org/certified-app
Length of output: 54
🏁 Script executed:
# Check if there's any documentation about AT Protocol's security model
rg -n 'at.protocol|atproto.*security|atproto.*encrypt' -i --type md | head -20Repository: hypercerts-org/certified-app
Length of output: 1423
🏁 Script executed:
# Read the privacy page
cat src/app/privacy/page.tsxRepository: hypercerts-org/certified-app
Length of output: 17652
Substantiate the "end-to-end encryption" claim or remove it from landing copy.
The landing page states "End-to-end encryption and user-controlled data," but the codebase, architecture documentation, and privacy policy provide no evidence of end-to-end encryption. The privacy policy (Section 11) describes only "reasonable technical and organizational measures" and notes that users are responsible for protecting their cryptographic keys—which are for AT Protocol identity authentication, not data encryption.
"User-controlled data" is accurate (AT Protocol provides data portability), but this is distinct from end-to-end encryption. E2E encryption requires client-side encryption before upload and prevents the server from reading the data; this implementation does not have that model. The infrastructure provider operates the PDS where data is stored plaintext-accessible to the server.
Either substantiate the e2e encryption claim with actual implementation, or reword to accurately reflect that the platform offers data portability and standard security (HTTPS transport, at-rest protection).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/landing/landing-page.tsx` around lines 95 - 96, Update the
landing copy in the LandingPage component
(src/components/landing/landing-page.tsx) to remove or correct the unsupported
"end-to-end encryption" claim: either delete the phrase "End-to-end encryption
and user-controlled data" or replace it with an accurate statement such as
"Standard security (HTTPS transport and at‑rest protections) and user-controlled
data/portability via AT Protocol"; ensure the hero pillar text
(hero-pillar__lede and its adjacent paragraph) matches this corrected wording so
the UI no longer asserts E2E encryption.
…ignment line The front page had three different left-edge alignment lines on desktop: the navbar + landing sections used a 1536px container, the hero typography column used a 1480px container plus an extra 32px inner padding, and the hero pillars row used a 1480px container plus 64px outer + 32px pillar padding. That produced a visibly staggered left edge — the navbar wordmark sat outside the hero text, and the '01 Protect' card was inset further still than the hero text. Introduce two CSS custom properties: - --container-max: 1280px (matches the existing footer) - --container-pad-x: 32px desktop / 20px <=768px / 16px <=380px …and route every landing-page container through them: .navbar__inner, .hero__split, .hero__copy (drop +32 internal pad), .hero__visual (drop +32 internal right pad), .hero-pillars__inner, .hero-pillar (first/last hug edge), .landing-section + __inner (move horizontal pad from outer to inner), .landing-footer + __inner + __bottom. Result: navbar logo, hero eyebrow / headline / subtitle / buttons / 'Built on AT Protocol', '01 Protect' icon, 'YOUR BENEFITS' / 'What you get' header, every other section header, and the footer brand all snap to one vertical line. Verified at 375 / 768 / 1024 / 1280 / 1440 / 1920 / 1995 px viewports — every primary element on the front page lands on the same x-coordinate at every breakpoint, and the layout is symmetric on the right edge too. The unified line is slightly more inset than the previous most-inset element, so the page reads as one calm column on wide desktops, per design feedback. No other pages affected: legal/info pages use the dedicated narrower .prose-page__inner layout, which is intentionally separate.
The hero diagram's blue watercolor PNG has a #fefefe rectangular bg which was visible against the cream #faf9f5 page surface — you could see the PNG's bounding box around the blob, especially at the corners. Set --color-surface and --color-off-white to #fefefe so the blob blends seamlessly into the page. Container tones (--color-surface- container / -low / -high) keep their warm hierarchy so cards, badges, and inset surfaces still read as distinct against the new near-white page bg.
There was a problem hiding this comment.
♻️ Duplicate comments (4)
AGENTS.md (4)
108-129:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd language identifiers to fenced code blocks to fix markdownlint/rendering consistency.
These fenced blocks are still missing language tags, so MD040 remains noisy and renderer behavior can vary.
Suggested minimal fix
-``` +```text ┌─────────────────┐ authFetch ┌──────────────────────┐ ... -``` +``` -``` +```html <html> ... -``` +``` -``` +```text certified-app/ ... -``` +```Also applies to: 142-164, 574-772
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AGENTS.md` around lines 108 - 129, AGENTS.md contains fenced code blocks without language identifiers (e.g., the ASCII authFetch diagram, the HTML snippet, and the certified-app directory listing), which triggers MD040; update each fence to include an appropriate language tag—use "text" for ASCII/diagram and directory-listing blocks and "html" for the HTML snippet—so the blocks around the shown diagram and the other affected ranges (lines referenced in the review: 108-129, 142-164, 574-772) are changed from ``` to ```text or ```html as appropriate and ensure closing fences remain triple backticks.
528-529:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
X-Frame-Options: DENYconflicts with the documented iframe OAuth callback flow.The same section says
/oauth/callbackruns in an iframe and usespostMessage, butDENYblocks all framing before JS executes. This line should either document that iframe callback is unsupported or point to a route/header exception.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AGENTS.md` around lines 528 - 529, The documentation currently states `X-Frame-Options: DENY` while also describing an iframe-based `/oauth/callback` flow, which is contradictory; update AGENTS.md to either change the header recommendation to allow same-origin framing (e.g., `X-Frame-Options: SAMEORIGIN`) or explicitly document an exception for the `/oauth/callback` route explaining that it must be exempted from the `DENY` header so the iframe + postMessage flow works; mention the `/oauth/callback` route by name and the `X-Frame-Options` header so reviewers can locate and reconcile the docs and implementation.
285-285:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winEscape literal pipes in the blob MIME text inside the table row.
image/jpeg|png|webpsplits the row into extra columns and breaks MD056 table parsing.Suggested minimal fix
-| `/api/groups/[groupDid]/upload-blob` | POST | yes | cookie | 5MB cap, allowed types `image/jpeg|png|webp`. | +| `/api/groups/[groupDid]/upload-blob` | POST | yes | cookie | 5MB cap, allowed types `image/jpeg\|png\|webp`. |🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AGENTS.md` at line 285, The table row's MIME types cell contains literal pipe characters ("image/jpeg|png|webp") which break Markdown table parsing; update that cell in AGENTS.md so the pipes are escaped (e.g., change the cell to use escaped pipes like image/jpeg\|png\|webp inside the same backticks) so the row remains a single table column; target the table row string /api/groups/[groupDid]/upload-blob and replace the unescaped pipe separators with escaped versions.
671-676:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRefresh the landing component map; it still references the legacy hero signin file.
The file map still points to
hero-signin-button.tsxand does not list the new hero components introduced in this PR, which will misroute contributors.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AGENTS.md` around lines 671 - 676, Update the landing component map in AGENTS.md under the landing/ directory: remove the legacy entry hero-signin-button.tsx and replace it with the new hero component filenames introduced in this PR (add each new hero file name exactly as in the diff, e.g., the new hero client/island components), and ensure the list still includes landing-page.tsx, home-client.tsx, orbiting-logos.tsx and the sections/ entry so contributors are routed to the correct files and folder structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@AGENTS.md`:
- Around line 108-129: AGENTS.md contains fenced code blocks without language
identifiers (e.g., the ASCII authFetch diagram, the HTML snippet, and the
certified-app directory listing), which triggers MD040; update each fence to
include an appropriate language tag—use "text" for ASCII/diagram and
directory-listing blocks and "html" for the HTML snippet—so the blocks around
the shown diagram and the other affected ranges (lines referenced in the review:
108-129, 142-164, 574-772) are changed from ``` to ```text or ```html as
appropriate and ensure closing fences remain triple backticks.
- Around line 528-529: The documentation currently states `X-Frame-Options:
DENY` while also describing an iframe-based `/oauth/callback` flow, which is
contradictory; update AGENTS.md to either change the header recommendation to
allow same-origin framing (e.g., `X-Frame-Options: SAMEORIGIN`) or explicitly
document an exception for the `/oauth/callback` route explaining that it must be
exempted from the `DENY` header so the iframe + postMessage flow works; mention
the `/oauth/callback` route by name and the `X-Frame-Options` header so
reviewers can locate and reconcile the docs and implementation.
- Line 285: The table row's MIME types cell contains literal pipe characters
("image/jpeg|png|webp") which break Markdown table parsing; update that cell in
AGENTS.md so the pipes are escaped (e.g., change the cell to use escaped pipes
like image/jpeg\|png\|webp inside the same backticks) so the row remains a
single table column; target the table row string
/api/groups/[groupDid]/upload-blob and replace the unescaped pipe separators
with escaped versions.
- Around line 671-676: Update the landing component map in AGENTS.md under the
landing/ directory: remove the legacy entry hero-signin-button.tsx and replace
it with the new hero component filenames introduced in this PR (add each new
hero file name exactly as in the diff, e.g., the new hero client/island
components), and ensure the list still includes landing-page.tsx,
home-client.tsx, orbiting-logos.tsx and the sections/ entry so contributors are
routed to the correct files and folder structure.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9ec6229f-86dc-4926-b05c-832ba8eee371
📒 Files selected for processing (4)
AGENTS.mdsrc/app/globals.csssrc/components/landing/hero-diagram.tsxsrc/components/landing/landing-page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/landing/landing-page.tsx
The italic accent word read better as a soft fade-out than a sentence-ended statement.
Replaces the placeholder partner cards (SkyFeed, WhiteWind, Flashes, Frontpage, Smoke Signal) on the hero identity diagram with the actual ecosystem the app advertises everywhere else (Ma Earth, GainForest, Simocracy, Hyperboards). Bluesky stays since it's the canonical atproto entry point. Changes: - Bluesky now uses the official butterfly mark in its native sky-blue fill, dropped into a new public/assets/partners/bluesky_logo.svg pulled from Wikimedia Commons. The previous stylised inline butterfly (a hand-rolled approximation) is removed. - Ma Earth / GainForest / Simocracy / Hyperboards use lucide-react icons (Sprout, Trees, Vote, Trophy) so they share a visual style with the rest of the diagram instead of mixing raster partner logos at 16px. - The 6th slot (right-middle) is now an open 'Your app' placeholder: dashed warm hairline border, italic muted text, sienna Plus icon. Reads as an invitation to new partners rather than a real product card. Two new CSS rules (.hero-diagram__app--placeholder and .hero-diagram__app-icon--bluesky) carry the styling. - Drops the obsolete inline icon components (FeedIcon, WindIcon, BoltIcon, DocIcon, SignalIcon) — they had no other consumers.
Local sign-in has never actually worked end-to-end against the
src/lib/auth/oauth-client.ts code path: the client_id was always
${PUBLIC_URL}/.well-known/oauth-client-metadata, and atproto's
NodeOAuthClient rejects http:// URLs unless they're the spec's
literal http://localhost loopback exception (no port, no path).
Pointing PUBLIC_URL at http://127.0.0.1:3000 in dev produced a
ZodError 'URL must use the "https:" protocol' on every login POST.
Detect loopback dev (NODE_ENV !== 'production' AND PUBLIC_URL is
missing or http://) and swap to buildAtprotoLoopbackClientMetadata,
which generates the virtual http://localhost?... client_id the spec
calls for. The redirect_uri is forced to http://127.0.0.1:<port>/
oauth/callback, since redirect_uris must be on 127.0.0.1 / [::1] in
loopback mode (yes, that's inverted from the client_id host rule —
it's the spec). The port is read from PUBLIC_URL, defaulting to 3000.
ATPROTO_PRIVATE_KEY is intentionally ignored in loopback dev: the
helper hard-codes token_endpoint_auth_method: 'none', so a keyset
would be unused. Production code path is unchanged.
Verified: with PUBLIC_URL=http://127.0.0.1:3000 set in .env.local
and the dev server restarted, POST /api/auth/login no longer throws
the Zod error. (End-to-end sign-in still needs real Upstash creds
in .env.local, which is unrelated.)
Also:
- .env.local.example now recommends http://127.0.0.1:3000 for dev
with a comment explaining why localhost is wrong.
- AGENTS.md §4 (env table) cross-links to the new pitfall.
- AGENTS.md §8 documents the loopback-dev branch in the OAuth client.
- AGENTS.md §22 grows pitfall #3a covering the localhost vs 127.0.0.1
inversion, the cookie-host trap, and the tunnel workaround for
branded dev.
Two fixes that unblock 'Sign in with Certified' and 'Sign in with ATProto/Bluesky' on localhost without external infrastructure. 1. Drop the hard-coded handleResolver: PDS_URL override. Passing handleResolver as a string forces NodeOAuthClient to call that PDS's com.atproto.identity.resolveHandle XRPC for every input. With PDS_URL=https://certified.one, that meant any handle certified.one didn't host (e.g. anything *.bsky.social) failed to resolve, the route fell back to client.authorize('https://<handle>') treating the handle as a PDS URL, and that 404'd on the /.well-known/oauth-protected-resource probe. The SDK default (AtprotoHandleResolverNode) already does the right thing — DNS-TXT lookup of _atproto.<handle> plus HTTPS GET to <handle>/.well-known/atproto-did — for any atproto handle in the network. Email-mode logins pass PDS_URL straight to authorize() so they never touch the handle resolver and aren't affected. 2. Add an InMemoryRedis fallback to src/lib/auth/stores.ts. When UPSTASH_REDIS_REST_URL/TOKEN are missing AND NODE_ENV !== 'production', getRedis() returns a process-local Map-backed store that satisfies the same { set(k, v, { ex }), get<T>, del } surface as the Upstash client. State doesn't survive restarts and isn't shared across workers — both fine for dev, both fatal for prod, so the production path still throws if creds are missing. A one-time console.warn fires on first use to keep the tradeoff visible. Verified end-to-end: with PUBLIC_URL=http://127.0.0.1:3000 and no Upstash creds, POST /api/auth/login on both modes returns 200 with a valid PDS authorize URL, and submitting the modal redirects to https://auth.certified.one/oauth/authorize where the real OTP screen renders. AGENTS.md §8 updated to reflect both changes.
Restore the architectural backdrop from the original certified.app/welcome — concentric rings centered on the hero plus corner-to-corner diagonals, layered on top of the existing 100x100 grid pattern. Bump .hero__pattern opacity from 0.04 (effectively invisible) to 0.18 so the lines actually read on the warm cream surface without competing with the editorial typography or the identity diagram on the right.
The painted PNG was RGB-only with a hard white background, and `mix-blend-mode: multiply` couldn't dissolve it because .hero__split sits in its own stacking context (z-index: 1) — multiply was isolated from the .hero__pattern grid in the parent section, so the blob's bounding box read as a clean white rectangle interrupting the grid. Bake a real alpha channel into the asset (alpha = 255 - min(r,g,b), which is the multiply-against-white identity) so the cream surface and architectural grid + rings flow continuously through the blob. Drops the now-unnecessary mix-blend-mode and bumps opacity from .55 to .65 to keep the wash density similar without the multiply darken. Also downsized the PNG from 1254x1254 RGB (2.0M) to 1024x1024 RGBA + pngquant (188K) — display size maxes at ~420px so 1024 still has 2x DPI headroom.
…gram The hero-diagram had an SVG connector layer (.hero-diagram__lines) drawn in an absolute 800x600 viewBox: dashed segments stopping at x=360/440 with little + joints at the corners, originally meant to link the central identity card to the six partner-app pills. The pills, however, are positioned with CSS percentages (left: 2% / right: 2%), so the lines never reached anywhere near them — they sat floating mid-air around the blob, breaking the architectural grid behind them and reading as 'random dashed marks with pluses'. Cleanest fix: drop the connector layer entirely (SVG + the dead .hero-diagram__lines CSS rule + its mobile override). The diagram still reads clearly: pills around the central card, a watercolor backdrop, a lock badge \u2014 grouping is obvious without explicit lines.
Brings in: - alignment fix (navbar / hero / pillars / sections share one left edge) - real Certified partners on the diagram (Bluesky, Ma Earth, GainForest, Simocracy, Hyperboards, + Your app placeholder) - page surface bumped to #fefefe (matches watercolor blob bg) - trailing period after 'atmosphere' dropped - atproto OAuth on localhost (loopback metadata + handle resolver fix) Conflict in src/components/landing/hero-diagram.tsx: origin/sharfy re-introduced the dotted connector layer (.hero-diagram__lines, the '---+' SVG). Resolved by keeping HEAD's removal — the connector endpoints (x=360/440 in the 800x600 viewBox) never reached the pills (positioned at left:2% / right:2%), so the lines floated unanchored across the diagram. The user explicitly asked to remove them, and origin/sharfy didn't move the pills to fix the alignment. Also dropped a dangling 'tiny inline icons' trailing comment that became orphaned when bbc9794 deleted the inline icon components.
`app.bsky.actor.searchActors` is a public read on the Bluesky AppView, so /api/search-actors no longer needs an authenticated atproto agent. Drop the auth gate entirely so signed-out visitors can use the upcoming navbar typeahead, and replace the AtpAgent round-trip (which was proxying through the user's PDS just to forward the call) with a direct `fetch` against https://public.api.bsky.app. - No session lookup, no DPoP, no Redis hit \u2014 the route is now a thin pass-through. - 5s upstream timeout via AbortSignal.timeout so a stuck AppView can't pin a serverless function. - 5xx upstream errors are sanitized to 'Search backend unavailable' per AGENTS.md \u00a717; 4xx pass through with their text. - Response shape unchanged: { actors: [{ did, handle, displayName, avatar }] }. - Filter out malformed entries (missing did or handle) before mapping so downstream consumers never see partial records.
A typeahead search bar that finds atproto identities and routes the
selection to /profile/<did>. Mounted in both the authenticated and
unauthenticated navbar branches so anyone visiting the site can search.
UX
- Debounced 250ms; stale responses are dropped via a request-sequence
ref so the dropdown only ever shows the most recent query's results.
- Keyboard
- ArrowDown / ArrowUp move the highlight (with wrap-around)
- Home / End jump to first / last
- Enter selects the highlighted row, or the first row if none is
highlighted (so just typing + Enter still works)
- Esc clears the query, and on a second press blurs the input
- Mouse hover and keyboard cursor share the same highlight state, but
the hover background is gray-only while the keyboard-highlighted row
also gets an inset 2px accent in --color-primary, so you can tell
which is which when both are in play.
- Selecting a result blurs the input on the way out, so the dropdown
doesn't reappear when the destination /profile/<did> page mounts.
- Plain `fetch` (not authFetch) so a transient upstream hiccup doesn't
trip the global 401 \u2192 sign-in interceptor for signed-out visitors.
A11y
- role=combobox + aria-controls + aria-autocomplete=list +
aria-expanded + aria-activedescendant.
- Dropdown is a proper role=listbox of role=option rows.
- Highlighted row scrolls into view on highlight change so keyboard
navigation can't push the cursor off-screen.
Visual
- Resting fill is var(--color-surface-container) with no border \u2014 the
same material the at://alexlee.dev DID chip in the hero uses, so the
pill reads as part of the editorial system rather than a control.
- Hover deepens to var(--color-surface-container-high).
- Focus snaps to flat white with a --border-medium edge and a soft
rgba(50,35,20,0.05) ring so the active state is unambiguous.
Responsive
- <=900px: max-width 320px, tighter margins
- <=768px: the desktop nav cluster (app-links / public-links / user)
collapses, so the search flexes into the freed middle
space (max-width none, margins 12px) instead of disappearing
- <=480px: shorter pill (36px), tighter padding, transparent
placeholder \u2014 the magnifying glass icon does the talking
when the navbar is tight
- <=380px: even tighter margins / padding
Previously, clicking a result wiped the input back to empty after navigating to /profile/<did>, so the user lost the visual confirmation of who they'd selected. Now we set the input to the selected actor's display name (or handle as fallback) and suppress the next debounced search so we don't immediately re-fire a request for the name we just set. onFocus is also tightened to only reopen the dropdown when there are results, so refocusing after a selection doesn't pop a stale or empty dropdown.
Big catchall branch that encompasses: redesign certified.app and any other missing certified.app features by @s-adamantine
Summary by CodeRabbit
New Features
Style
Documentation
Content