Skip to content

feat(seo): Tier C identity migration + ProfilePage schema#400

Merged
mergify[bot] merged 4 commits into
mainfrom
feat/seo-tier-c-identity
May 17, 2026
Merged

feat(seo): Tier C identity migration + ProfilePage schema#400
mergify[bot] merged 4 commits into
mainfrom
feat/seo-tier-c-identity

Conversation

@julianken
Copy link
Copy Markdown
Owner

Summary

Tier C identity migration. Site author identity changes from pseudonymous "detached-node" to dual identity "Julian (detached-node)". Brand "detached-node" stays as siteName. Real name appears in JSON-LD Person schema, the new ProfilePage schema on /about, the HTML <meta name=author>, and visible bylines on posts + patterns.

Changes

Schema

  • src/lib/schema/config.tsAUTHOR_CONFIG replaced with Tier C shape: real name, expanded description, jobTitle, broad knowsAbout array. LinkedIn URL placeholder (linkedin.com/in/julian-kennon) — actual profile pending creation; flagged below.
  • src/lib/schema/types.tsPersonSchema interface extended with optional jobTitle and knowsAbout; new ProfilePageSchema interface.
  • src/lib/schema/person.tsgeneratePersonSchema spreads new fields.
  • src/lib/schema/profile-page.ts (new) — generateProfilePageSchema() for /about.
  • src/lib/schema/index.ts — barrel exports for new generator + type.

Visible identity

  • src/app/(frontend)/about/page.tsx<SchemaScript> swaps Person for ProfilePage (both branches).
  • src/app/(frontend)/layout.tsx:44 — HTML authors meta now uses "Julian Kennon".
  • src/app/(frontend)/posts/[slug]/page.tsx — visible byline "Julian (detached-node)" with rel="author" under post date.
  • src/components/agentic-patterns/PatternHeader.tsx — small unobtrusive byline footer.

Tests

  • tests/unit/lib/schema/profile-page.test.ts (new, 11 cases) — covers ProfilePage emission, mainEntity Person, @id structure, sameAs/knowsAbout fresh-copy invariants.

Manual post-merge steps for Julian

  1. Create LinkedIn profile with handle linkedin.com/in/julian-kennon (or update sameAs in a follow-up PR if the handle differs).
  2. Update About page Payload DB record — paste the drafted About copy that identifies you as Julian. The DB record currently shows a one-line placeholder; the hardcoded fallback in about/page.tsx never displays while a DB record exists.

Coordination

  • B (fix(seo): immediate EEAT and crawl-surface fixes #393) merged: sameAs already on julianken in main. This PR's full AUTHOR_CONFIG replacement supersets that.
  • The LinkedIn URL is intentionally a placeholder — schema still validates as well-formed; if the URL eventually 404s, that's a one-line follow-up.

Local smoke verification

pnpm dev + curl spot-checks:

  • /about view-source: "@type":"ProfilePage", "name":"Julian Kennon", "jobTitle":"Software Engineer", embedded Person with knowsAbout array — all present
  • /: <meta name="author" content="Julian Kennon"> present; Person schema reflects real name
  • /posts/where-agentic-patterns-actually-live: visible "Julian (detached-node)" byline with rel="author" — 2 hits as expected (FadeReveal renders twice in SSR)
  • /agentic-design-patterns/prompt-chaining: visible byline with rel="author" — present

Test plan

  • pnpm typecheck — clean
  • pnpm lint (incl. lint:adp) — 0 errors (warnings pre-existing)
  • pnpm test:unit — 536/536 pass (incl. new 11-case ProfilePage suite)
  • Local smoke: ProfilePage JSON-LD renders on /about; meta author updated; bylines render on posts + patterns
  • CI (10 required checks)

Closes #388

Migrate site author from pseudonymous 'detached-node' to dual identity
'Julian (detached-node)':
- AUTHOR_CONFIG.name -> 'Julian Kennon' with expanded description, jobTitle,
  knowsAbout array (deliberately broad: includes Distributed systems,
  TypeScript, Full-stack software engineering to mitigate niche-pigeonholing)
- sameAs gains LinkedIn placeholder (URL pending profile creation)
- New ProfilePage schema on /about, embedding Person as mainEntity
- HTML <meta name=author> updated to 'Julian Kennon'
- Visible byline 'Julian (detached-node)' on post + pattern pages, rel=author
- Brand 'detached-node' stays as siteName/brand

Schema-level migration. The About page Payload DB record is unchanged
by this PR -- Julian updates the visible About copy manually post-merge.

Closes #388
@julianken julianken added status:in-review PR open, waiting for review area:seo SEO + AI-discovery strategy work labels May 17, 2026
julianken-bot
julianken-bot previously approved these changes May 17, 2026
Copy link
Copy Markdown
Collaborator

@julianken-bot julianken-bot left a comment

Choose a reason for hiding this comment

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

Verdict: APPROVE

Tier C identity swap is structurally clean. Entity graph integrity preserved: AUTHOR_CONFIG.id continues to be referenced from WebSite.creator, BlogPosting.author/publisher, Article.author, and the new ProfilePage.mainEntity — no @id mismatch introduced. New ProfilePage schema is well-typed, the test suite locks down its shape and reference-freshness, and the visible bylines (post + pattern pages) wire to /about with rel="author". Findings below are non-blocking refinements; nothing here gates merge.

Verification ledger (commands I ran)

  • pnpm exec vitest run (in PR-branch worktree) — 536/536 passed (32 files), including the new 11-case profile-page.test.ts
  • pnpm typecheckclean (no tsc output)
  • pnpm lint + pnpm lint:adp0 errors, 18 pre-existing warnings (all in unrelated test files / tests/e2e/*)
  • grep -rn AUTHOR_CONFIG — confirmed all 4 callers (website.ts, blog-posting.ts, agentic-patterns.ts, profile-page.ts) still reference AUTHOR_CONFIG.id correctly; entity graph coherent
  • grep generatePersonSchema — homepage at src/app/(frontend)/page.tsx:23 still emits the standalone Person schema, now with the new jobTitle/knowsAbout fields; about page swaps to ProfilePage (both branches: fallback + DB-backed)
  • curl github.com/julianken — 200; curl linkedin.com/in/julian-kennon — 999 (LinkedIn's anti-bot status, can't distinguish exists/404; the PR body discloses this is a placeholder, ack)
  • check-mermaid-render.sh julianken/detached-node 400{"ok":true,"total":0} (no Mermaid blocks in PR body; R15 trivially passes)
  • gh pr view 400 --json baseRefOid,headRefOid — base f46cf14, head 5fa4ade (unchanged since review start)

Findings ToC

  • IMPORTANT 1 — Missing alternateName: "detached-node" on Person; visible bylines say "Julian (detached-node)" but JSON-LD has no machine-readable alias for the brand
  • SUGGESTION 1 — Embedded Person in ProfilePage.mainEntity carries its own @context; Google's documented example omits it (harmless but divergent)
  • SUGGESTION 2 — File-header comment in config.ts (lines 5-13, unchanged) still describes the migration as pending while the AUTHOR_CONFIG body now embodies the post-migration state

What this PR got right (specific, not filler)

The mainEntity.sameAs and mainEntity.knowsAbout spreads (and the corresponding not.toBe reference-freshness assertions in the test) are the right call — without them, a future caller mutating the schema array would silently corrupt the const config. That's the failure mode worth pinning down with tests, and you did.

Bottom line

Approve. Address Finding 1 (alternateName) in this PR if quick, or as a 2-line follow-up — it's the only one with an actual EEAT signal at stake. SUGGESTIONs 1 and 2 are pure cleanup.

@julianken-bot (opus, fresh context)

Comment thread src/lib/schema/config.ts
name: "detached-node",
// Canonical author page — the semantic owner of the Person schema
// Tier C identity migration: real name in schema; brand "detached-node" stays as siteName.
name: "Julian Kennon",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

IMPORTANT — Missing Person.alternateName for the brand alias.

AUTHOR_CONFIG.name is now "Julian Kennon" and JSON-LD emits that on / (Person), /about (ProfilePage.mainEntity), and via <meta name="author">. But the visible bylines you added in posts/[slug]/page.tsx and PatternHeader.tsx render "Julian (detached-node)", and siteName stays "detached-node". There is no machine-readable bridge between the legal name in structured data and the brand string that readers actually see — grep -rn alternateName src/ tests/ returns zero hits in the PR-branch worktree.

Person.alternateName is the canonical schema.org field for this. Adding it lets entity-graph consumers (Google Knowledge Graph, Bing, ClaudeBot, GPTBot) link "detached-node" mentions back to the same @id as the real-name Person, which is exactly the EEAT signal Tier C is targeting.

// src/lib/schema/config.ts
export const AUTHOR_CONFIG = {
  name: "Julian Kennon",
  alternateName: "detached-node",
  // ...
} as const;

Then surface it on both emission paths (PersonSchema interface in types.ts, the spread in person.ts, and the embedded mainEntity in profile-page.ts). One test in the new profile-page.test.ts would lock it down.

Not gating merge — addressable as a 2-line follow-up — but this is the only finding that closes a real signal gap, not a stylistic one.

Comment thread src/lib/schema/profile-page.ts Outdated
name: `About ${AUTHOR_CONFIG.name} — ${SITE_CONFIG.name}`,
isPartOf: { "@id": SITE_CONFIG.websiteId },
mainEntity: {
"@context": "https://schema.org",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SUGGESTION — Embedded Person carries its own @context.

The rationale comment in types.ts:71-78 is honest about this being a divergent choice, and it's harmless: Google's parser tolerates duplicate @context on nested entities. But Google's own ProfilePage example shows @context only at the top level — the embedded Person has just @type and properties. The current emission produces ~30 extra bytes per ProfilePage response and runs slightly against the documented shape.

If you want to align with Google's example, the embedded mainEntity becomes Omit<PersonSchema, "@context"> and the spread in profile-page.ts:22 drops the line. The test's mainEntity["@context"] assertion in profile-page.test.ts:71 would invert to not.toHaveProperty("@context").

Strictly cleanup — the current shape validates and renders. Filing as SUGGESTION because the comment explicitly notes "no @context-stripping convention exists in this codebase for embedded entities yet"; this PR is a reasonable place to establish one, but the cost of deferring is zero.

Comment thread src/lib/schema/config.ts
// Update name to real name or final pseudonym when identity is decided.
name: "detached-node",
// Canonical author page — the semantic owner of the Person schema
// Tier C identity migration: real name in schema; brand "detached-node" stays as siteName.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SUGGESTION — File-header comment is stale.

This PR correctly updates the in-block comment here ("Tier C identity migration: real name in schema..."), but the file-header block at lines 5-13 (unchanged, outside diff) still reads:

AUTHOR IDENTITY DECISION POINT: The author name and sameAs values are set here. When identity is finalized, update: 1. AUTHOR_CONFIG.name — real name or finalized pseudonym, 2. sameAs, 3. description.

That framing describes a pre-migration state that no longer exists — name is now "Julian Kennon", sameAs has the LinkedIn entry, description is the expanded biography. A future maintainer reading the top of this file will think the migration is still pending.

Suggested rewrite: shorten the header to a one-line summary of the current state ("Author entity config — real name in JSON-LD; brand-only as siteName") and drop the migration checklist. Pure doc-rot cleanup.

- Add alternateName: 'detached-node' to AUTHOR_CONFIG, thread through
  PersonSchema interface, generatePersonSchema, and generateProfilePageSchema.
  Bridges visible byline 'Julian (detached-node)' to schema-resolvable alias.
- Drop redundant @context from embedded mainEntity Person in ProfilePage,
  matching Google's documented example. mainEntity now typed as
  Omit<PersonSchema, "@context"> on ProfilePageSchema.
- Refresh stale file-header comment in config.ts to post-migration framing.
- Update profile-page test assertions: new alternateName check, replace
  embedded-@context assertion with negative check, mock includes alternateName.

Addresses bot review on #400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
julianken-bot
julianken-bot previously approved these changes May 17, 2026
Copy link
Copy Markdown
Collaborator

@julianken-bot julianken-bot left a comment

Choose a reason for hiding this comment

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

Verdict: APPROVE

Tier C identity migration is structurally clean. Entity-graph integrity preserved: all five schemas (Person, ProfilePage.mainEntity, WebSite.creator, BlogPosting.author/publisher, Article.author) still point at the single canonical ${siteUrl}/#author node. Type system extended cleanly (PersonSchema gains three optionals; ProfilePageSchema introduced with mainEntity: Omit<PersonSchema, "@context"> so the embedded copy can't accidentally double-declare @context). The three findings from the prior bot review (alternateName missing, embedded Person carrying redundant @context, stale config.ts header comment) have all been addressed at de36d39.

Verification ledger

  • pnpm vitest run tests/unit/lib/schema/profile-page.test.ts → 12 tests pass (PR body says "11 cases" — trivial undercount)
  • pnpm vitest run tests/unit/lib/schema → 43 tests pass across 3 files
  • pnpm tsc --noEmit → clean
  • pnpm lint (incl. lint:adp) → 0 errors (18 pre-existing warnings unchanged)
  • Entity-graph @id audit: grep -n 'AUTHOR_CONFIG.id' src/lib/schema/*.ts → all references resolve to the same canonical id
  • PR body has no mermaid blocks (R15 n/a) and no injection patterns (R11 clean)
  • R13 drift surfaces not touched (no routes/migrations/types/specs)
  • R14 css contract: new classNames are pure Tailwind utilities, no custom .class introduced

Findings

Severity File Note
SUGGESTION src/lib/schema/profile-page.ts:33 DRY: mainEntity rebuilt by hand; de36d39 already paid the drift cost once
SUGGESTION src/lib/schema/person.ts:30 No person.test.ts exists; new fields on homepage Person schema are uncovered by CI

Neither blocks merge. Both are post-merge polish.

Bottom line

Cleared to merge. The LinkedIn-URL-as-placeholder is honestly disclosed in the PR body and a soft-flag at worst; if the URL eventually 404s, the one-line follow-up is genuinely one line.

@julianken-bot (Opus 4.7)
Same-tier risk noted: implementer fixup at de36d39 was also Opus. R8 mandatory-find executed; two suggestions surfaced from second pass.

description: AUTHOR_CONFIG.description,
jobTitle: AUTHOR_CONFIG.jobTitle,
knowsAbout: [...AUTHOR_CONFIG.knowsAbout],
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SUGGESTION — ProfilePage.mainEntity rebuilds the Person entity by hand.

generateProfilePageSchema() (this function, lines 22-33) and generatePersonSchema() in src/lib/schema/person.ts:17-30 both spell out the same @type/@id/name/alternateName/url/sameAs/description/jobTitle/knowsAbout shape. When AUTHOR_CONFIG grows another field tomorrow (e.g. image, worksFor), both call sites have to be updated — and that drift cost is not hypothetical: the immediately-prior fixup commit (de36d39) had to touch both files just to thread alternateName through. The type Omit<PersonSchema, "@context"> already names the embedded shape; a small buildPersonEntity({ includeContext: boolean }) helper (or simpler: destructure @context from generatePersonSchema() for the embedded copy) would collapse the duplication and prevent future drift. Non-blocking; flagging as a follow-up.

Comment thread src/lib/schema/person.ts
// Spread to avoid mutating the const array
sameAs: [...AUTHOR_CONFIG.sameAs],
description: AUTHOR_CONFIG.description,
jobTitle: AUTHOR_CONFIG.jobTitle,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SUGGESTION — No unit coverage for generatePersonSchema() after the new fields land.

The Tier C additions (alternateName, jobTitle, knowsAbout) now flow through both generatePersonSchema() (used on /) and generateProfilePageSchema() (used on /about), but tests/unit/lib/schema/ only adds profile-page.test.ts — there is no test file for person.ts at all (verified: find tests -name 'person*.test.*' returns nothing). The new fields are confirmed via manual smoke on the homepage in the PR body, but a future refactor that drops e.g. knowsAbout from generatePersonSchema() would render on / with missing data and the CI suite wouldn't notice. A small tests/unit/lib/schema/person.test.ts mirroring the ProfilePage suite's assertions on the new fields closes the gap.

@julianken
Copy link
Copy Markdown
Owner Author

@Mergifyio queue

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 17, 2026

Merge Queue Status

  • 🟠 Waiting for queue conditions
  • ⏳ Enter queue
  • ⏳ Run checks
  • ⏳ Merge
Required conditions to enter a queue
  • -closed [📌 queue requirement]
  • -conflict [📌 queue requirement]
  • -draft [📌 queue requirement]
  • any of [📌 queue -> configuration change requirements]:
    • -mergify-configuration-changed
    • check-success = Configuration changed
  • any of [🔀 queue conditions]:
    • all of [📌 queue conditions of queue rule default]:
      • #approved-reviews-by >= 1
      • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
      • #changes-requested-reviews-by = 0 [🛡 GitHub branch protection]
      • -conflict
      • -draft
      • base = main
      • check-success = Analyze Bundle
      • check-success = CodeQL Analysis
      • check-success = E2E Shard 1/4
      • check-success = E2E Shard 2/4
      • check-success = E2E Shard 3/4
      • check-success = E2E Shard 4/4
      • check-success = ESLint
      • check-success = Next.js Build
      • check-success = TypeScript
      • check-success = Vitest
      • github-review-decision = APPROVED [🛡 GitHub branch protection]
      • any of [🛡 GitHub branch protection]:
        • check-success = ESLint
        • check-neutral = ESLint
        • check-skipped = ESLint
      • any of [🛡 GitHub branch protection]:
        • check-success = TypeScript
        • check-neutral = TypeScript
        • check-skipped = TypeScript
      • any of [🛡 GitHub branch protection]:
        • check-success = Vitest
        • check-neutral = Vitest
        • check-skipped = Vitest
      • any of [🛡 GitHub branch protection]:
        • check-success = Next.js Build
        • check-neutral = Next.js Build
        • check-skipped = Next.js Build
      • any of [🛡 GitHub branch protection]:
        • check-success = Analyze Bundle
        • check-neutral = Analyze Bundle
        • check-skipped = Analyze Bundle
      • any of [🛡 GitHub branch protection]:
        • check-success = CodeQL Analysis
        • check-neutral = CodeQL Analysis
        • check-skipped = CodeQL Analysis
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 1/4
        • check-neutral = E2E Shard 1/4
        • check-skipped = E2E Shard 1/4
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 2/4
        • check-neutral = E2E Shard 2/4
        • check-skipped = E2E Shard 2/4
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 3/4
        • check-neutral = E2E Shard 3/4
        • check-skipped = E2E Shard 3/4
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 4/4
        • check-neutral = E2E Shard 4/4
        • check-skipped = E2E Shard 4/4

The Tier C migration added a visible byline <p> element to the post detail
header, directly after the existing date <p>. The page-object selector used
`article header p`.last(), which was fragile — it assumed only one <p> in
the header.

Update the selector to <article header p:not(:has(a[rel='author']))>, which
explicitly excludes the byline regardless of JSX order. Production
rendering is unchanged.

Addresses E2E Shard 3/4 failure on #400.
Copy link
Copy Markdown
Collaborator

@julianken-bot julianken-bot left a comment

Choose a reason for hiding this comment

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

Verdict: APPROVE

Tier C identity migration is mechanically correct end-to-end. AUTHOR_CONFIG real-name swap, new ProfilePage generator, Person-schema extension, both /about branches updated, HTML <meta name=author>, visible bylines on posts and patterns, and the e2e postDate selector rewritten to dodge the new byline <p> — all consistent.

The entity graph is intact: standalone Person on / and embedded Person inside ProfilePage.mainEntity on /about both emit @id: ${siteUrl}/#author, and ProfilePage.isPartOf points at ${siteUrl}/#website which matches generateWebSiteSchema().@id. The intentional @context omission on the embedded Person matches the Google-documented ProfilePage example (spot-checked against developers.google.com/search/docs/appearance/structured-data/profile-page during review).

Verification ledger (this turn)

  • pnpm test:unit on PR head (d6deaa9): 537/537 passed — includes the new 11-case profile-page.test.ts suite
  • pnpm typecheck: 0 errors
  • pnpm lint: 0 errors, 18 pre-existing warnings (unchanged)
  • CI on PR: ESLint, TypeScript, Vitest, Next.js Build, Analyze Bundle, CodeQL all SUCCESS; E2E shards 1-4 IN_PROGRESS at review time
  • Google ProfilePage docs cross-checked: mainEntity.Person correctly omits @context

Findings

  1. SUGGESTIONprofile-page.ts:23-33 duplicates generatePersonSchema() body; consider destructuring instead (see inline). Polish, not blocking.

Specific praise

  • alternateName on both the standalone Person (person.ts:25) and the embedded mainEntity Person (profile-page.ts:27) — keeps the entity-graph alias resolvable from either entry point rather than only from the about page. Good call.
  • Existing test mocks in agentic-patterns.test.ts and breadcrumb-hub.test.ts weren't updated, and they don't need to be: those suites don't call generatePersonSchema(), so the stale AUTHOR_CONFIG shape in their mocks is incidental. Resisting the urge to "fix" them was correct.
  • The e2e postDate selector article header p:not(:has(a[rel="author"])) is a defensive rewrite — it tracks "the date <p>" by what it is not (the author byline), not by positional .last(). Robust against future header additions that would have invalidated the old selector silently.

Same-tier risk

Implementer model unknown from the diff; if both implementer and reviewer ran opus, the bot applied extra R8 skepticism (mandatory second pass with explicit prior). Result of the second pass: one real SUGGESTION-tier finding above; nothing reaches IMPORTANT or BLOCKER.

Bottom line

Approve. The single finding is future-proofing; merging without addressing it is fine.

@julianken-bot (anti-slop rubric)

description: AUTHOR_CONFIG.description,
jobTitle: AUTHOR_CONFIG.jobTitle,
knowsAbout: [...AUTHOR_CONFIG.knowsAbout],
},
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

SUGGESTION — DRY: the embedded mainEntity Person here duplicates the construction in generatePersonSchema() (src/lib/schema/person.ts:17-32) field-for-field, minus @context. Eight fields are repeated across both files.

The type system catches a missing required field on mainEntity (it is typed as Omit<PersonSchema, "@context">), but it will not catch a future optional field being added to one site and not the other (e.g. image, email, worksFor).

A small refactor folds them together:

export function generateProfilePageSchema(): ProfilePageSchema {
  // Reuse the standalone Person body; `@context` is owned by the top-level ProfilePage.
  const { "@context": _ctx, ...person } = generatePersonSchema();
  return {
    "@context": "https://schema.org",
    "@type": "ProfilePage",
    "@id": `${SITE_CONFIG.url}/about#profile-page`,
    url: `${SITE_CONFIG.url}/about`,
    name: `About ${AUTHOR_CONFIG.name}${SITE_CONFIG.name}`,
    isPartOf: { "@id": SITE_CONFIG.websiteId },
    mainEntity: person,
  };
}

Non-blocking — every emitted field is correct today, and the two existing sites stay in sync as long as AUTHOR_CONFIG is the only source of truth for both. Future-proofing only.

@mergify mergify Bot added the queued label May 17, 2026
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 17, 2026

Merge Queue Status

  • Entered queue2026-05-17 19:15 UTC · Rule: default
  • Checks passed · in-place
  • Merged2026-05-17 19:19 UTC · at d86de0a985cd7eef80ce001da09a1612c4bea8ab · squash

This pull request spent 3 minutes 36 seconds in the queue, including 3 minutes 6 seconds running CI.

Required conditions to merge
  • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
  • #changes-requested-reviews-by = 0 [🛡 GitHub branch protection]
  • github-review-decision = APPROVED [🛡 GitHub branch protection]
  • any of [🛡 GitHub branch protection]:
    • check-success = ESLint
    • check-neutral = ESLint
    • check-skipped = ESLint
  • any of [🛡 GitHub branch protection]:
    • check-success = TypeScript
    • check-neutral = TypeScript
    • check-skipped = TypeScript
  • any of [🛡 GitHub branch protection]:
    • check-success = Vitest
    • check-neutral = Vitest
    • check-skipped = Vitest
  • any of [🛡 GitHub branch protection]:
    • check-success = Next.js Build
    • check-neutral = Next.js Build
    • check-skipped = Next.js Build
  • any of [🛡 GitHub branch protection]:
    • check-success = Analyze Bundle
    • check-neutral = Analyze Bundle
    • check-skipped = Analyze Bundle
  • any of [🛡 GitHub branch protection]:
    • check-success = CodeQL Analysis
    • check-neutral = CodeQL Analysis
    • check-skipped = CodeQL Analysis
  • any of [🛡 GitHub branch protection]:
    • check-success = E2E Shard 1/4
    • check-neutral = E2E Shard 1/4
    • check-skipped = E2E Shard 1/4
  • any of [🛡 GitHub branch protection]:
    • check-success = E2E Shard 2/4
    • check-neutral = E2E Shard 2/4
    • check-skipped = E2E Shard 2/4
  • any of [🛡 GitHub branch protection]:
    • check-success = E2E Shard 3/4
    • check-neutral = E2E Shard 3/4
    • check-skipped = E2E Shard 3/4
  • any of [🛡 GitHub branch protection]:
    • check-success = E2E Shard 4/4
    • check-neutral = E2E Shard 4/4
    • check-skipped = E2E Shard 4/4

@mergify mergify Bot merged commit 38f5052 into main May 17, 2026
13 checks passed
@mergify mergify Bot deleted the feat/seo-tier-c-identity branch May 17, 2026 19:19
@mergify mergify Bot removed the queued label May 17, 2026
@julianken
Copy link
Copy Markdown
Owner Author

@Mergifyio queue

@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 17, 2026

Merge Queue Status

  • 🟠 Waiting for queue conditions
  • ⏳ Enter queue
  • ⏳ Run checks
  • ⏳ Merge
Waiting for
  • -closed [📌 queue requirement]
All conditions
  • -closed [📌 queue requirement]
  • -conflict [📌 queue requirement]
  • -draft [📌 queue requirement]
  • any of [📌 queue -> configuration change requirements]:
    • -mergify-configuration-changed
    • check-success = Configuration changed
  • any of [🔀 queue conditions]:
    • all of [📌 queue conditions of queue rule default]:
      • #approved-reviews-by >= 1
      • #approved-reviews-by >= 1 [🛡 GitHub branch protection]
      • #changes-requested-reviews-by = 0 [🛡 GitHub branch protection]
      • -conflict
      • -draft
      • base = main
      • check-success = Analyze Bundle
      • check-success = CodeQL Analysis
      • check-success = E2E Shard 1/4
      • check-success = E2E Shard 2/4
      • check-success = E2E Shard 3/4
      • check-success = E2E Shard 4/4
      • check-success = ESLint
      • check-success = Next.js Build
      • check-success = TypeScript
      • check-success = Vitest
      • github-review-decision = APPROVED [🛡 GitHub branch protection]
      • any of [🛡 GitHub branch protection]:
        • check-success = ESLint
        • check-neutral = ESLint
        • check-skipped = ESLint
      • any of [🛡 GitHub branch protection]:
        • check-success = TypeScript
        • check-neutral = TypeScript
        • check-skipped = TypeScript
      • any of [🛡 GitHub branch protection]:
        • check-success = Vitest
        • check-neutral = Vitest
        • check-skipped = Vitest
      • any of [🛡 GitHub branch protection]:
        • check-success = Next.js Build
        • check-neutral = Next.js Build
        • check-skipped = Next.js Build
      • any of [🛡 GitHub branch protection]:
        • check-success = Analyze Bundle
        • check-neutral = Analyze Bundle
        • check-skipped = Analyze Bundle
      • any of [🛡 GitHub branch protection]:
        • check-success = CodeQL Analysis
        • check-neutral = CodeQL Analysis
        • check-skipped = CodeQL Analysis
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 1/4
        • check-neutral = E2E Shard 1/4
        • check-skipped = E2E Shard 1/4
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 2/4
        • check-neutral = E2E Shard 2/4
        • check-skipped = E2E Shard 2/4
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 3/4
        • check-neutral = E2E Shard 3/4
        • check-skipped = E2E Shard 3/4
      • any of [🛡 GitHub branch protection]:
        • check-success = E2E Shard 4/4
        • check-neutral = E2E Shard 4/4
        • check-skipped = E2E Shard 4/4

mergify Bot pushed a commit that referenced this pull request May 17, 2026
The Tier C identity migration (#400) shipped with a placeholder LinkedIn
URL because the actual profile didn't exist yet. Julian confirmed his
real handle: julian-k-ba6b5897. Update both AUTHOR_CONFIG.sameAs and the
docs/seo-strategy/drafts/julianken-profile-readme.md draft.

A placeholder/404 URL in sameAs is an active-negative signal to Google's
entity graph - same class of issue the original broken github.com/detached-node
sameAs had. This closes the entity-graph gap.

Closes #403
mergify Bot pushed a commit that referenced this pull request May 18, 2026
* chore(docs): drop seo-strategy folder; align README to renamed post slug

Removes docs/seo-strategy/ — research artifacts from the SEO + AI-
discovery analysis funnel, no longer load-bearing now that the
gate-1/2/3 work has shipped (#393 #394 #395 #396 #397 #400 #402 #404
#406 #408). History preserved in git.

README: align "Recent essays" entry with the renamed post
slug (where-agentic-patterns-actually-live →
agentic-patterns-in-your-coding-workflow). The rename satisfies Bing
Site Scan's 70-char title cap.

No redirect deployed — article is two days old, no significant
external link equity to preserve.

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

* fix: defer README slug update; gitignore docs/seo-strategy

Address julianken-bot review of PR #409:

BLOCKER (README:75) — New slug URL serves an SSR 404 fallback because
the Payload post slug hasn't been renamed yet (intentionally deferred
until the in-flight Bing Site Scan completes). Reverting the README
link change here; it will land in a follow-up PR after the actual
Payload slug rename, so the link is never broken in main.

Plus: add /docs/seo-strategy/ to .gitignore so future analysis-funnel
artifacts (phase-*, context-packets, STATUS.md, issues/) stay on disk
without polluting the index.

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:seo SEO + AI-discovery strategy work status:in-review PR open, waiting for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(seo): migrate to Tier C identity (real name + bylines + ProfilePage schema)

2 participants