Sync the wahoo-import "FIT to GPX conversion" requirement into the main
spec and move the completed change to
openspec/changes/archive/2026-07-16-fit-parsing-hardening.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
All 13 tasks complete (implementation shipped in #570–573; staging
verification in #574). Archive via `openspec archive` (CLI 1.6.0):
- Creates openspec/specs/federation-operations/spec.md — the new
capability: durable federation queue, inbound replay defense, instance
blocklist, published protocol doc, delivery observability (Purpose
filled in; CLI leaves a TBD placeholder).
- Applies the MODIFIED requirement to openspec/specs/social-federation:
"Push delivery on local activity create" now guarantees persistent
queueing + a "Fan-out survives a deploy" scenario.
- Moves the change to openspec/changes/archive/2026-07-13-federation-hardening/.
Both specs pass `openspec validate --strict`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The route-surface-breakdown change is fully implemented (18/18 tasks,
all artifacts done). Archive it via `openspec archive` (CLI 1.6.0):
- Moves the change to openspec/changes/archive/2026-07-13-route-surface-breakdown/
- Promotes its 5 delta requirements into a new capability spec at
openspec/specs/route-surface-breakdown/spec.md (surface/waytype
breakdown from BRouter waytags, async Overpass backfill, live SSE
update, proportion bars, localized labels)
Purpose paragraph filled in (the CLI leaves a TBD placeholder). Both the
spec and archived change pass `openspec validate --strict`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
poi-index shipped and is live in production (self-hosted planet POI index,
8.4M rows serving /api/pois; Overpass removed from the Planner). Archive the
change and apply its spec deltas:
- new capability: poi-index
- osm-poi-overlays: POIs from the instance index, not Overpass
- rate-limiting: /api/pois limit replaces the Overpass proxy limit
- infrastructure: POI extract (BRouter host) + import (flagship) components
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both changes are implemented and merged (#539, #541). Promote their deltas into
openspec/specs/, move the changes to changes/archive/2026-06-14-*, and add the
two capabilities to the CAPABILITIES index.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Archive the three implemented + merged changes (activity-sport-type,
activity-stats, journal-elevation-profile): their deltas are promoted into
openspec/specs/, the changes move to changes/archive/2026-06-12-*, and the
three new capabilities are added to the CAPABILITIES index.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- komoot-import: add SHALL to Credential storage requirement body
- local-dev-environment: add SHALL to Mobile app dev requirement body
- multi-day-routes: demote ### Note heading to plain paragraph
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- authentication-methods: document completeAuth mode param ("redirect"|"json");
clarify add-passkey nudge (no dismiss mechanism, disappears on passkey add)
- journal-auth: session maxAge is 30 days; terms allow-list uses /legal/ prefix
matching (broader than fixed list of paths)
- session-notes: mark awareness isolation and UndoManager isolation as not yet
implemented (shared instances in current code)
- activity-feed: add fan-out scenario for visibility change to public
- explore: note that ?perPage is not yet implemented (hardcoded page size)
- multi-day-routes: add per-day GPX track split scenario (splitByDays option);
document overnight vs isDayBreak naming gap
- osm-poi-overlays: debounce is 800ms + 2000ms min interval (not 500ms);
retry is not automatic (fires on next viewport change)
- brouter-integration: rate limit corrected to 300/hour; add segment-cache
requirement (client caches per-pair segments)
- wahoo-route-push: OAuth state shape uses camelCase (returnTo, pushAfter
object) not snake_case with push_after boolean
- komoot-import: document noop adapter / ConnectedServiceManager bypass;
note four Komoot-specific routes that bypass the generic OAuth framework
- background-jobs: exponential backoff not wired (retryLimit only); add SIGINT
- connected-services: add revoked status; name ConnectionNotActiveError
- infrastructure: add INTEGRATION_SECRET and SENTRY_DSN to env var lists;
split secret decryption scenario by workflow (cd-apps vs cd-infra)
- secret-management: correct CD decryption — cd-apps only decrypts app.env;
cd-infra decrypts both
- transactional-emails: welcome email is async (pg-boss job); magic-link email
includes 6-digit numeric code
- journal-route-detail: websites are https: links (not mailto:); opening_hours
is also displayed
- local-dev-environment: add mobile app (Expo) dev commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add INTEGRATION_SECRET to journal service in docker-compose.yml with
:? guard so a missing value fails loudly at compose-up time
- Add INTEGRATION_SECRET to E2E test step in ci.yml via GitHub secret
(unit tests already set their own value in the test file)
- Archive openspec/changes/komoot-import → archive/2026-05-23-komoot-import
- Sync delta specs: new openspec/specs/komoot-import/spec.md,
updated openspec/specs/route-management/spec.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Breaks the monolithic planner.test.ts (581 lines, 25 tests) into five
focused files grouped by feature area, with BRouter mocked by default
via test.beforeEach in files that need routing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix openspec/specs/waypoint-notes/spec.md: replace delta-spec header
with proper ## Purpose + ## Requirements structure so validation passes
- Fix GPX export E2E assertion: check gpxText directly instead of regex
matching on lat coordinate; use click-elsewhere-to-blur instead of
.blur() for more reliable note save timing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Staging environments are live and fully operational. Marks the remaining
verification tasks complete, syncs the delta spec to main specs, and
archives the change.
Also adds a "When to Use Local vs. Staging" reference table to the
local-dev-environment spec so the boundary between the two environments
is documented in one place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Applies the ADDED requirement from
openspec/changes/unify-auth-completion/specs/authentication-methods/
into openspec/specs/authentication-methods/spec.md:
- Single web auth completion chokepoint (completeAuth at
apps/journal/app/lib/auth/completion.server.ts) with five scenarios
covering passkey register/login finish, magic-link verify-code,
magic-link click-through, and returnTo sanitization.
Path in the synced requirement is .server.ts (the post-rename name),
not the .ts the delta originally captured.
Change moved to openspec/changes/archive/2026-05-08-unify-auth-completion/.
15/15 tasks complete.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fold the wahoo-route-update delta into openspec/specs/wahoo-route-push/spec.md
(POST→PUT logic with 404 fallback, stable external_id, push-status UI) and
move the change directory to openspec/changes/archive/. Task 4.3 (Playwright
E2E) skipped — contract is fully covered by unit/integration tests in
wahoo.test.ts and pushes.server.test.ts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fold completed deltas into main specs (activity-feed, route-management,
infrastructure, planner-session), add new background-jobs and
demo-activity-bot capability specs, and move the three change dirs to
openspec/changes/archive/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move completed wahoo-route-push change to archive and sync delta specs:
update wahoo-import for routes_write scope, add new wahoo-route-push capability.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Outcome of today's `/spec-drift-review`. Concentrated on the
high-and-medium-severity items; low-severity wording left for the
next per-feature change to pick up naturally.
High-severity URL fixes (specs were actively misleading):
- account-management — email-change verification URL was specced as
`/auth/verify-email-change?token=...`; the actual route is
`/auth/verify?email-change=1&token=...` (see auth.verify.tsx:8).
A reader implementing against the old wording would build a link
that 404s.
- observability — "Both apps SHALL expose a /metrics endpoint" was
half right: the planner is at /metrics, but the journal exposes
/api/metrics. The infrastructure spec already had the right URLs;
the observability spec disagreed with itself. Now both correct,
with a one-line note explaining the per-app split.
Medium-severity wording drift (Stream E aftermath):
- profile-settings, account-management, connected-services — all
three said "the settings page SHALL include a [...] section",
which described the old single-scrollable settings page. Stream E
(PR #323) split /settings into four sub-pages
(/settings/{profile,account,security,connections}); rewording each
spec to point at its specific sub-page. The API endpoints
(/api/settings/*) and behavior are unchanged.
- authentication-methods — passkey add/delete previously said "via
the settings page"; now specifically /settings/security.
Code drift fixed inline:
- apps/journal/app/routes/auth.verify.tsx — after a successful
email change, the redirect was going to `/settings#account` (an
anchor on the OLD single-scrollable settings page). Stream E
retired that page; the right destination now is
`/settings/account`. Without this the user would land on
/settings/profile (which is what /settings redirects to) instead
of the page that just changed.
sse-broker spec wording:
- The "no buffering tweaks in the Caddy reverse_proxy block"
scenario asserted the entry was "a plain `reverse_proxy
journal:3000`". After PR #329 the journal block has
`lb_try_duration 30s` / `lb_try_interval 250ms` — neither affects
streaming, so SSE still works, but the spec's "plain" language
was no longer literally true. Reworded to forbid only
buffering-related directives; explicitly call out that
retry-on-restart directives like lb_try_duration are fine.
Navbar consolidation (journal-landing):
- The shipped navbar's full shape was scattered: notifications said
"navbar has a bell," explore said "navbar has an Explore entry,"
but no spec described the avatar dropdown, the primary-nav
cluster (Feed/Routes/Activities), or the mobile drawer (Stream
C / PR #324). Added a "Top navbar shape" requirement to
journal-landing covering all of it — anonymous vs signed-in,
desktop vs mobile, dropdown contents, drawer behavior. The
per-feature specs (notifications, explore) still own their own
badges/entries; this requirement just says what the whole
cluster looks like.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The directory was filtering out the demo persona on the rationale
that "the demo bot is not a real user and should not appear in real
discovery." That's exactly backwards — the whole point of having a
demo persona is to give new users a follow target so the platform
doesn't feel empty when they arrive. Hiding the bot from the
discovery surface defeats its purpose.
Concretely on flagship: only one local user (ullrich) was visible
on /explore today, even though Bruno (the demo persona) is
public-by-default and posting public activities. After this change
both appear; Bruno carries a small "🐕 Demo account" badge next to
his display name so viewers know what they're following.
- apps/journal/app/lib/explore.server.ts — drop the
ne(users.username, persona.username) clause from exclusionFilters.
The demo persona is now treated like any other public user. Banned/
suspended scaffolding stays for forward-compat.
- apps/journal/app/routes/explore.tsx — loader computes isDemoUser
per row (cheap, just username comparison against
loadPersona().username). DirectoryRow renders the demo badge inline
with the display name, matching the existing pattern on
/users/:username.
- openspec/specs/explore/spec.md — updated the "Excluded users"
requirement to remove the demo persona, replaced the "demo
excluded" scenario with "demo appears with badge", and updated
the "Active recently" requirement + scenarios accordingly.
- apps/journal/app/lib/explore.integration.test.ts — flipped the
demo-persona test from "is excluded" to "is included".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Promotes the deltas from openspec/changes/add-explore-page/ into
top-level specs after the implementation landed in #321.
- New top-level spec: openspec/specs/explore/spec.md (paginated local
user directory at /explore, with the "Active recently" sub-section,
exclusion rules for private profiles and the demo persona, offset
pagination, and the navbar entry rule).
- Updated openspec/specs/journal-landing/spec.md with the new
"Visitor home links to /explore" requirement.
- Updated openspec/CAPABILITIES.md with an entry under Social.
- Moved openspec/changes/add-explore-page/ to
openspec/changes/archive/2026-04-26-add-explore-page/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stream A from docs/information-architecture.md: signed-in users now
have a single /feed destination with two views — Followed (default,
people they accepted-follow) and Public (instance-wide). Logging in no
longer hides the public instance feed; switching is a query-param flip
that's bookmarkable and SSR-rendered.
- apps/journal/app/routes/feed.tsx — loader reads ?view=, branches
fetch (listSocialFeed vs listRecentPublicActivities, both already
exist), passes view to the component. Component renders a tab strip
at the top using plain <Link>s so the toggle works without JS. Per-
view <meta> title and empty state. The "see public feed" escape
from the empty Followed view now points at ?view=public instead of
/, keeping the user on /feed.
- packages/i18n/src/locales/{en,de}.ts — new social.feed.toggle.{ },
social.feed.public.{heading,empty}, and social.feed.seePublic
keys; old social.feed.publicFeedLink renamed to seePublic.
- openspec/specs/social-follows/spec.md — Social activity feed
requirement extended with the two-view structure, including the
Public view, the toggle, and the unrecognized-value fallback.
- openspec/specs/activity-feed/spec.md — Instance-wide public
activity feed requirement notes the Public view of /feed is now a
consumer alongside the visitor home.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The navbar already has a "Feed" entry that signed-in users see on every
page, including /. The page-header button next to "New Activity"
duplicated that path without adding anything — they pointed at the same
URL, with the navbar entry being the more discoverable of the two.
Stream D from docs/information-architecture.md.
- apps/journal/app/routes/home.tsx — remove the Feed anchor; "New
Activity" stays as the only header CTA on the personal dashboard.
- openspec/specs/journal-landing/spec.md — retire the "Social feed
link for signed-in users" requirement that prescribed the now-deleted
button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Folds the actionable follow-requests surface into the Notifications page
as a Requests tab (alongside the existing Activity tab), so the navbar
exposes a single bell instead of two adjacent inboxes. The Requests tab
shows a count badge for pending rows regardless of read state, while the
bell badge keeps reflecting the unread-notifications count (which already
covers `follow_request_received` rows). The standalone /follows/requests
URL is preserved as a 301 redirect so prior notification deep-links,
emails, and bookmarks still resolve.
Driven by the IA review captured in docs/information-architecture.md.
Specs (notifications, social-follows, journal-landing) are updated in
the same change to reflect the new structure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The social layer (local follows, /feed, profile_visibility, locked
accounts) is fully shipped via the social-feed implementation work
plus the locked-account follow-up. Closing out the change.
Pre-archive ticks:
- 1.4: schema migrated on prod via cd-apps drizzle-kit push; column
+ table verified on the running DB.
- 3.3: follower/following counts on /users/:username shipped in #310.
- 7.1: cd-apps drizzle-kit push --force ran; verified post-deploy.
- 7.2: smoke inputs verified (bruno is public on prod, has 17 public
activities, /users/bruno returns 200). Live click-through is
operator-discretion; the listSocialFeed query correctness is proven
by integration tests.
- 7.3 / 7.4: forward-pointers, not deliverables for this change.
One task explicitly deferred:
- 6.2: full activity-creation E2E for the /feed assertion. Equivalent
coverage at the integration level + the e2e Follow-button +
visibility tests; not worth wiring an e2e activity-creation helper
just for this one path.
Spec sync:
+ journal-landing: 1 added
~ public-profiles: 1 added, 1 modified
+ social-follows: new spec (5 added)
Move to openspec/changes/archive/2026-04-25-social-feed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final tasks ticked post-merge:
- 10.2: verified on prod that journal.routes + journal.activities still
default to 'private' NOT NULL, with the only public rows being the
15-each demo-bot seeded content
- 10.3: demo-activity-bot already inserts with visibility='public'
directly in demo-bot.server.ts
Syncs the three delta specs into main:
+ activity-feed: 2 added, 1 modified
+ public-profiles: new spec (1 added)
+ route-management: 2 added, 1 modified
Moves change to openspec/changes/archive/2026-04-24-public-content-visibility.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously a signed-in user landing on `/` saw the same page as an
anonymous visitor: the visitor hero, the flagship marketing cards, and
the instance-wide public feed. "Home" should mean "your stuff."
The home route now branches on session: signed-out visitors keep the
marketing + public-feed layout from #303; signed-in users get a
personal dashboard showing their own activities reverse-chronologically
(all visibilities), a welcome line linking to their profile, and a
"New Activity" CTA. The public instance feed and marketing blurbs are
suppressed for signed-in users — they'd be redundant on a personal
landing surface.
Loader picks `listActivities(user.id)` vs `listRecentPublicActivities`
based on session, so only one feed query runs per request.
Updates the `journal-landing` spec with the new session-based split and
a "Personal dashboard for signed-in users" requirement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The old home was an h1, subtitle, and two auth buttons — visitors
arriving at trails.cool had no idea what it was, and self-hosted
instances had nothing interesting to land on.
The new home is one layout that serves both audiences:
- Hero: a product-describing h1 ("Federated outdoor journal") +
tagline. The site name already lives in the top banner and nav brand,
so the h1 carries the pitch instead of triplicating "trails.cool".
- Auth CTAs: Register (blue) + Sign In (outlined) get primary weight.
Planner demotes to a small "Or try the Planner without an account →"
link below them — it's a nice escape hatch but not the goal.
- Marketing cards: on the flagship only, a 2x2 grid of emoji-icon cards
for Planner / Journal / Federation / Ownership, matching the Planner
home's visual weight. `IS_FLAGSHIP=true` toggles it. Self-hosters get
a "Powered by trails.cool — about the project" link back to the
flagship instead, so they don't have to write their own marketing.
- Public feed: reverse-chronological list of the 20 most recent public
activities on the instance, with owner + distance + date. Empty state
for fresh installs.
Plumbing:
- `listRecentPublicActivities(limit)` in activities.server.ts joins
users so the feed can render "by <displayName>" in one query.
- `IS_FLAGSHIP` env wired through docker-compose.yml; cd-apps and
cd-infra both write `IS_FLAGSHIP=true` alongside `DOMAIN=trails.cool`,
so self-hosters (who deploy without these workflows) default to off.
Specs:
- `activity-feed`: new "Instance-wide public activity feed" requirement
so the visibility contract is pinned (public in feed, unlisted +
private are not).
- `journal-landing`: new small spec capturing the hero / marketing /
feed layout contract and the `IS_FLAGSHIP` toggle, so future
instance-branding or empty-state changes have a stable anchor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final step (task 9.4) of the BRouter relocation. The change is moved to
openspec/changes/archive/2026-04-24-relocate-brouter-to-dedicated-host/
and the four delta specs (brouter-integration, infrastructure,
observability, security-hardening) are merged into the main specs:
+ 6 added requirements
~ 4 modified requirements
The two intentionally-unchecked tasks are 2.2 (obsoleted, see crossed-out
note) and 9.4 itself (this archive).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The three pre-legal-disclaimer users (ullrich, pistazie, nelli) have
NULL terms_version, and any future Terms update would leave every
existing user in the same state. Close the loop now that we have
version storage by redirecting any logged-in user whose
users.terms_version doesn't match the currently-published
TERMS_VERSION to a dedicated acceptance page.
Changes:
- auth.server: new recordTermsAcceptance(userId, version) helper that
writes both terms_accepted_at and terms_version.
- root loader: if the session user has a stale or NULL terms_version,
throw redirect("/auth/accept-terms?returnTo=<pathname>") unless the
request is already on an allow-listed path
(/auth/accept-terms, /auth/logout, /legal/*) so Terms are reachable
and logout works.
- New route /auth/accept-terms (GET renders the prompt, POST records
acceptance and bounces to a sanitised returnTo). Same-origin check
on returnTo to avoid open-redirect abuse. Logout button is provided
as an escape hatch.
- i18n: new auth.reaccept.* keys for EN and DE.
- Spec: new Requirement + five scenarios (redirect, allow-list,
successful re-accept, missing consent, returnTo sanitisation).
No action on the three legacy users is required beyond what they'll
experience on their next visit — the gate takes care of it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reviewer's follow-up: it's not enough to record when a user accepted
the Terms; we also need to record which version of the text they saw.
Changes:
- journal.users gains a nullable `terms_version` text column (nullable
so the three pre-existing users without a version are kept as-is).
- New apps/journal/app/lib/legal.ts exports TERMS_VERSION as a single
source of truth, reused by the legal pages' "Last updated" header
and by the registration flow as the value to send/store.
- Registration form posts `termsVersion` alongside `termsAccepted` on
all three relevant steps (start, finish, register-magic-link).
- API route validates that `termsVersion` is a non-empty string on
any step that requires terms, and forwards it to the auth server.
- auth.server finishRegistration and registerWithMagicLink now take
`termsVersion` and persist it on the users row.
- journal-auth spec gets a new scenario for version storage and a
rejection scenario for missing version.
PRIVACY_LAST_UPDATED is also exported from the same module and used
by the Privacy page header, keeping both pages on a single legal.ts
source of truth for "last updated" labels. Privacy is not per-user
stored — it's informational, not contract.
Existing users have NULL terms_version; if we ever prompt them to
re-accept updated Terms, we can backfill with the version they
re-accept at that point.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes one contradiction and closes five gaps where the code (after the
legal-review rounds in #235, #246, #247) now exceeds what the spec
required.
Contradiction fixed:
- Alpha banner was specified as dismissible via sessionStorage, with
"reappears in a new session". In reality the dismiss control and
sessionStorage were removed during privacy hardening — the banner
is now always visible. Updated the requirement to match.
New scenarios documenting what's already shipped:
- Impressum: EU ODR link + non-participation notice; English
translation noted as informational.
- Terms of Service: service availability, minimum age 16 for Journal
accounts, content usage-rights licence, acceptable-use rules
(including no mass automated data extraction), German-law-tiered
liability, bilingual structure.
- Datenschutzerklärung: explicit data-categories-and-purposes list;
Terms acceptance classified under Art. 6(1)(b) and explicitly NOT
(1)(a); dedicated server-logs section (≤14 days, Art. 6(1)(f));
storage-durations list; alpha-reset caveat; per-third-party
disclosures with transfer details (Sentry, OSM tile servers,
Overpass proxy, BRouter, SMTP); Art. 44 ff. DSGVO third-country
transfer note for Sentry with SCC basis; Berlin supervisory
authority; Privacy Manifest appendix.
- Footer: now includes a link to the source repository on both apps.
No functional changes — only spec text catching up with what is live
in production.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move completed legal-disclaimers change to archive, apply deltas to
main specs: append "Terms acknowledgement at signup" requirement to
journal-auth and create the legal-disclaimers capability spec
covering Impressum, Terms, Datenschutzerklärung, alpha banner, and
footer links.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Main specs (7): Converted from delta format (## ADDED/MODIFIED) to
proper main spec format (## Purpose + ## Requirements)
Changes (3): Added missing spec files for e2e-test-reorganization,
journal-poi-details, and mobile-nearby-sync
Mobile testing: Added missing scenario for "No Vitest" requirement
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Synced new spec (chart ↔ map interaction, mobile touch, iOS safe area,
responsive header) to openspec/specs/elevation-map-interaction/.
All 4 artifacts complete. All 14 tasks complete.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>