Skip to content

Design changes #48

Open
s-adamantine wants to merge 21 commits intomainfrom
sharfy
Open

Design changes #48
s-adamantine wants to merge 21 commits intomainfrom
sharfy

Conversation

@s-adamantine
Copy link
Copy Markdown
Contributor

@s-adamantine s-adamantine commented May 5, 2026

Big catchall branch that encompasses: redesign certified.app and any other missing certified.app features by @s-adamantine

telegram-cloud-photo-size-4-6039360008803257950-y

Summary by CodeRabbit

  • New Features

    • Redesigned landing hero with visual diagram, trust pillars, and updated CTAs
    • New landing action components and updated hero layout; improved unauthenticated navbar links
    • Reworked footer with multi-column navigation
  • Style

    • Overhauled color palette, typography, global layout tokens, and refreshed landing UI
  • Documentation

    • Replaced repository guidance with a comprehensive canonical operations and API/security guide
  • Content

    • Simplified and reorganized About, Privacy, Terms, and DSA pages

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).
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

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

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.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 5, 2026

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

Project Deployment Actions Updated (UTC)
certified-app Ready Ready Preview, Comment May 6, 2026 11:23am

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The 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 (HeroActions, HeroDiagram), removes a legacy signin component, refactors policy pages to prose-page, updates Tailwind config and fonts, and replaces AGENTS.md with a comprehensive developer/architecture/ops guide. .gitignore also gains .pi/.

Changes

Landing & Public Site Redesign

Layer / File(s) Summary
Design tokens & global styles
src/app/globals.css, tailwind.config.ts
Color palette, neutral/surface tokens, border opacities and container sizing replaced with warm editorial tokens; new .btn primitives and .prose-page styles added; Tailwind theme.extend.colors and fontFamily updated.
Typography wiring
src/app/layout.tsx
Swapped web-font imports to IBM_Plex_Sans/IBM_Plex_Mono; body class updated to use new CSS font variables.
Hero layout & components
src/components/landing/landing-page.tsx, src/components/landing/hero-actions.tsx, src/components/landing/hero-diagram.tsx, src/components/landing/hero-signin-button.tsx
Hero refactored to hero__split: left editorial copy with HeroActions (new), right visual HeroDiagram (new). Legacy HeroSignInButton removed. Three-pillar trust strip added.
Landing sections & CTAs
src/components/landing/sections/*
CTA and ready/what-you-get sections restyled: cream variant, primary/secondary button patterns, updated card icon sizes and copy.
Navbar & footer
src/components/layout/navbar.tsx, src/components/layout/footer.tsx
Navbar unauthenticated view now shows center anchor links and right-side "Sign in"/"Get started" buttons; Footer rebuilt to multi-column brand/nav/bottom layout.
Policy pages (presentation only)
src/app/about/page.tsx, src/app/privacy/page.tsx, src/app/dsa/page.tsx, src/app/terms/page.tsx
Refactored from app-page/utility classes to prose-page layout; content reflowed/expanded (About adds comparison & socials; Privacy adds retention/rights; DSA adds infra clarifications; Terms reorganized).

Developer Documentation (separate DAG)

Layer / File(s) Summary
Canonical repo/architecture guide
AGENTS.md
Replaced with a comprehensive canonical instruction document: table of contents, environment variable contracts (e.g., PUBLIC_URL), architecture/data-flow, provider/layout wiring, explicit routing and edge redirect behavior, detailed authentication/session lifecycle (login → OAuth callback → iframe postMessage → session restore/logout with CSRF/origin checks), full API/XRPC proxy allowlists and blob/write limits, security rules, coding/UI conventions, file map, limitations and operational checklists.

Misc / Config

Layer / File(s) Summary
Git ignore
.gitignore
Added .pi/ entry.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through styles, fonts, and hue,
Swapped buttons, blobs, and headers too.
A split hero blooms with identity shown,
One account, many apps — your data your own.
Cheers from a rabbit with pixels aglow.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Design changes' is generic and vague, failing to communicate the scope or nature of the substantial UI/branding redesign across multiple components, pages, and CSS variables. Use a more specific title that captures the key design initiative, such as 'Redesign landing page and update color palette to editorial theme' or 'Rebrand UI with new color tokens and hero layout'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sharfy

Comment @coderabbitai help to get the list of available commands and usage tips.

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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (2)
src/components/landing/landing-page.tsx (1)

68-74: 💤 Low value

Consider using Link for the in-page anchor for consistency.

The footnote link uses <a href="#built-for-trust"> while ready-cta-button.tsx uses <Link href="#partner-apps"> for similar in-page navigation. Using Link consistently 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 value

Consider using lucide-react icons for consistency.

The inline SVGs (Users, User, Lock, Mail icons) have direct equivalents in lucide-react which 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, Mail

As per coding guidelines: "Use lucide-react for 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

📥 Commits

Reviewing files that changed from the base of the PR and between bfaa40c and a4c4fcc.

📒 Files selected for processing (17)
  • AGENTS.md
  • src/app/about/page.tsx
  • src/app/dsa/page.tsx
  • src/app/globals.css
  • src/app/privacy/page.tsx
  • src/app/terms/page.tsx
  • src/components/landing/chop-seal.tsx
  • src/components/landing/hero-actions.tsx
  • src/components/landing/hero-diagram.tsx
  • src/components/landing/hero-signin-button.tsx
  • src/components/landing/landing-page.tsx
  • src/components/landing/sections/ready-cta-button.tsx
  • src/components/landing/sections/ready-cta-content.tsx
  • src/components/landing/sections/what-you-get.tsx
  • src/components/layout/footer.tsx
  • src/components/layout/navbar.tsx
  • tailwind.config.ts
💤 Files with no reviewable changes (1)
  • src/components/landing/hero-signin-button.tsx

Comment thread AGENTS.md
Comment thread AGENTS.md
Comment on lines +522 to +527
`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=()`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 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.tsx

Repository: 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.

Comment thread AGENTS.md
Comment thread src/app/dsa/page.tsx
Comment on lines 188 to 192
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment thread src/app/terms/page.tsx
Comment on lines +303 to 306
<a href="/dsa">
DSA Compliance Page
</a>
.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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).

Comment thread src/components/landing/chop-seal.tsx Outdated
Comment on lines +406 to +413
<>
{/* 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>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/app/globals.css (2)

2330-2340: ⚡ Quick win

Remove duplicated mobile media rule for .landing-network.

The second @media (max-width: 768px) block repeats the same grid-template-columns assignment 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 win

Simplify hero CTA spacing to avoid wrap edge-cases.

Line 665-670 sets gap: 0 and then spacing is reintroduced via sibling margins at Line 1346-1350. This is fragile when buttons wrap at intermediate widths. Prefer using gap consistently.

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 140f4d32-b9b4-4062-a969-87dcb89d3643

📥 Commits

Reviewing files that changed from the base of the PR and between a4c4fcc and 7270080.

📒 Files selected for processing (1)
  • src/app/globals.css

Comment thread src/app/globals.css Outdated
Comment thread src/app/globals.css
border: 0;
border-top: 1px solid var(--color-light-gray);
border-radius: 0;
transition: border-color 300ms ease-out;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7270080 and 672a5d7.

⛔ Files ignored due to path filters (1)
  • public/assets/watercolor-blue-circle.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • .gitignore
  • src/app/globals.css
  • src/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

Comment thread src/app/globals.css
Comment on lines 809 to +813
.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; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 --glob '*.tsx' 'hero__split|hero__inner|hero-reveal' src/components/landing src/app

Repository: hypercerts-org/certified-app

Length of output: 584


🏁 Script executed:

rg -n -A 20 'hero__split' src/components/landing/landing-page.tsx | head -60

Repository: hypercerts-org/certified-app

Length of output: 1029


🏁 Script executed:

rg -n -B 5 -A 3 'hero-reveal' src/components/landing/landing-page.tsx

Repository: 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.

Comment thread src/app/globals.css
Comment thread src/app/globals.css
Comment on lines +2095 to +2099
.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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
.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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/components/landing/hero-diagram.tsx (1)

15-16: ⚡ Quick win

Use next/image for 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 and next/image for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 672a5d7 and d543948.

📒 Files selected for processing (5)
  • src/app/globals.css
  • src/app/layout.tsx
  • src/components/landing/hero-diagram.tsx
  • src/components/landing/landing-page.tsx
  • tailwind.config.ts

Comment thread src/components/landing/hero-diagram.tsx
Comment on lines +95 to +96
<p className="hero-pillar__lede">Secure by design</p>
<p>End-to-end encryption and user-controlled data.</p>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 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 src

Repository: 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 -20

Repository: 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 -30

Repository: 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 -40

Repository: 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 -20

Repository: 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 -10

Repository: 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 -10

Repository: 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 -20

Repository: 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 -20

Repository: hypercerts-org/certified-app

Length of output: 1423


🏁 Script executed:

# Read the privacy page
cat src/app/privacy/page.tsx

Repository: 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.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (4)
AGENTS.md (4)

108-129: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add 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: DENY conflicts with the documented iframe OAuth callback flow.

The same section says /oauth/callback runs in an iframe and uses postMessage, but DENY blocks 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 win

Escape literal pipes in the blob MIME text inside the table row.

image/jpeg|png|webp splits 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 win

Refresh the landing component map; it still references the legacy hero signin file.

The file map still points to hero-signin-button.tsx and 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

📥 Commits

Reviewing files that changed from the base of the PR and between d543948 and dc0c95b.

📒 Files selected for processing (4)
  • AGENTS.md
  • src/app/globals.css
  • src/components/landing/hero-diagram.tsx
  • src/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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant