Skip to content

Add & redesign friggframework.org marketing site under website/#630

Merged
seanspeaks merged 17 commits into
nextfrom
claude/migrate-static-website-frigg-w5i6ub
Jul 24, 2026
Merged

Add & redesign friggframework.org marketing site under website/#630
seanspeaks merged 17 commits into
nextfrom
claude/migrate-static-website-frigg-w5i6ub

Conversation

@seanspeaks

Copy link
Copy Markdown
Contributor

Summary

Migrates the friggframework.org marketing site into the Frigg core monorepo (from lefthook--demo-frigg-application) so it can be iterated on alongside the framework and deployed from Netlify, and rebuilds the page for the current framework story.

What's here

  • website/ — the marketing site as a single, self-contained static page (inline CSS + vanilla JS, no build step, no framework runtime), plus the Netlify serverless functions in website/friggframework-api/ (subscribe, submission-created).
  • netlify.toml (repo root) — base = "website", functions dir, and the original api. / feedback. redirects preserved from the previous deploy config.

Redesign highlights

  • New positioning: agents build the integrations; every integration is also an MCP tool your agents can call (typed capabilities / mcp-tool surface); own your stack and your pipes; wisdom of the crowd; zero-to-production in an afternoon; notes on easy adoption.
  • A "woven pipes" design (nod to Frigg, weaver of the clouds): animated weave canvas, a live agent-session terminal, a real integration-icon marquee, editorial "the shift" blocks, and a numbered zero-to-prod timeline.
  • Full light / dark / system theme toggle via CSS custom-property tokens (prefers-color-scheme default + persisted data-theme override).
  • Self-hosted fonts (woff2) — no external font requests. Existing GA + PostHog analytics preserved.
  • The signup form keeps the exact field names + form-name the submission-created function reads, so the Netlify Forms flow is unchanged.
  • Pruned ~8.7 MB of now-unused vendor assets (Bootstrap, jQuery, plugins, legacy fonts, old css/js); site dropped from ~9.8 MB to ~1.4 MB.

Notes for reviewers

  • This is a website-only change (new website/ dir + root netlify.toml); it touches no packages. I intentionally did not add the release/prerelease labels so it doesn't trigger an npm prerelease — add them only if your release tooling expects them here.
  • Build verified locally in both themes; all asset references resolve.

Test plan

  • netlify.toml base/publish/functions resolve correctly in a Netlify build
  • Light/dark/system toggle works and persists
  • Signup form submission is captured by Netlify Forms and reaches the submission-created function
  • Redirects (api. / feedback.) behave as before

🤖 Generated with Claude Code


Generated by Claude Code

claude added 3 commits July 24, 2026 00:48
Migrate the static marketing site (previously in the
lefthook--demo-frigg-application repo) into the Frigg core monorepo so it can
be iterated on alongside the framework and deployed from the next branch via
Netlify.

- website/ contains the static site (index.html, css/js/fonts/plugins/assets),
  accordions.json copy, and the Netlify serverless functions in
  friggframework-api/ (subscribe, submission-created).
- Root netlify.toml sets base = "website" with the functions directory and the
  api/feedback redirects preserved from the original deploy config.
- Add website/README.md and website/.gitignore.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
The shortcut icon pointed to assets/img/favicon.svg, which does not exist in
the site assets. Point it at the actual file, assets/img/frigg-favicon.svg.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
Rebuild the friggframework.org landing page from the old Bootstrap/jQuery
version into a single, self-contained, dependency-free page:

- New positioning: agents build the integrations; every integration is also an
  MCP tool your agents can call (typed capabilities / mcp-tool surface); own
  your stack and your pipes; borrow the wisdom of the crowd; zero-to-production
  in an afternoon; notes on easy adoption.
- Creative "woven pipes" design (nod to Frigg, "weaver of the clouds"):
  animated weave canvas, live agent-session terminal, real integration-icon
  marquee, editorial "the shift" blocks, a numbered zero-to-prod timeline.
- Full light / dark / system theme toggle via CSS custom-property tokens
  (prefers-color-scheme default + persisted data-theme override).
- Self-hosted fonts (Bricolage Grotesque, Hanken Grotesk, JetBrains Mono) as
  woff2 — no external font requests. Analytics (GA + PostHog) preserved.
- Signup form keeps the exact field names + form-name the submission-created
  function reads, so the Netlify Forms flow is unchanged.
- Prune ~8.7MB of now-unused vendor assets (Bootstrap, jQuery, plugins, legacy
  fonts, old css/js, accordions.json); site drops from ~9.8MB to ~1.4MB.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

Claude Code Review is paused for this repository. To reconnect it, an admin of this repository's GitHub organization (or the account owner, for personal repositories) who can also manage your Claude organization's Code Review settings needs to re-link GitHub in Code Review settings. This is a one-time step.

Tip: disable this comment in your organization's Code Review settings.

@cursor

cursor Bot commented Jul 24, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@gitguardian

gitguardian Bot commented Jul 24, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 1 secret following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secret in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
35143847 Triggered PostHog Project API Key e082e5d website/index.html View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secret safely. Learn here the best practices.
  3. Revoke and rotate this secret.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Comment on lines +53 to +59
const res = await fetch(process.env.WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify({email: payload.email.trim(),
slackInvite: payload.slackInvite,
emailUpdates: payload.emailUpdates}),
contentType: 'application/json'
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The contentType property is not a valid option for the fetch API. This will be silently ignored, causing the Zapier webhook request to be sent without the correct Content-Type header, which may cause the webhook to fail.

const res = await fetch(process.env.WEBHOOK_URL, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        email: payload.email.trim(),
        slackInvite: payload.slackInvite,
        emailUpdates: payload.emailUpdates
    })
})
Suggested change
const res = await fetch(process.env.WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify({email: payload.email.trim(),
slackInvite: payload.slackInvite,
emailUpdates: payload.emailUpdates}),
contentType: 'application/json'
})
const res = await fetch(process.env.WEBHOOK_URL, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email: payload.email.trim(),
slackInvite: payload.slackInvite,
emailUpdates: payload.emailUpdates})
})

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already resolved in the current head — subscribe.js sends headers: { 'Content-Type': 'application/json' }, not the invalid contentType option, so the request goes out with the correct content type.


Generated by Claude Code

Comment thread website/index.html Fixed
Comment thread website/index.html Fixed
Comment thread website/index.html Fixed
Comment thread website/index.html Fixed
Comment thread website/index.html Fixed
claude added 2 commits July 24, 2026 01:40
- Reposition "own your pipes" as cloud-agnostic: primarily AWS today, with
  adopters on GCP, Azure, and self-hosted containers.
- New "infrastructure writes itself" block: the Frigg app definition
  self-scaffolds its own resources (queues, functions, VPC, encryption, OAuth)
  from the integrations you define.
- New "however you run it" section covering the three real adopter patterns:
  product integrations for end customers, internal business-process
  automation, and personal / home-lab automation.
- New Left Hook commercial-license band (open core): MIT for everyone, plus a
  commercial license for support, maintenance, and premium/heavy-duty
  connectors, plugins & extensions.
- Blend Left Hook branding: orange (#ff5e00) is now the secondary accent
  (hero gradient, threads, timeline), a navy commercial band, and the official
  Left Hook hook mark in the commercial band and footer.
- Hero meta updated to "MIT + commercial" and "Runs on your cloud"; nav gains
  Uses + Commercial.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
- Header brand is now a full "Frigg by Left Hook" lockup: the Frigg mark and
  wordmark, then the official Left Hook hook mark and "Left Hook" wordmark.
- Fix a mobile header overflow: on <=680px the Docs button is hidden and the
  primary CTA shortens to "Join" so the brand, CTA, and theme toggle all fit;
  below 460px the "by / Left Hook" words drop, leaving the two marks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
Comment on lines +2 to +19
exports.handler = async function (event, context, callback) {
const payload = JSON.parse(event.body).payload
console.log(JSON.parse(event.body))
console.log(payload)
console.log(payload.data["slack-invite"])
const email = payload.data.email
const slackInvite = !!payload.data["slack-invite"]
const emailUpdates = !!payload.data["update-emails"]
console.log(email, slackInvite, emailUpdates)
const res = await fetch('https://api.friggframework.org/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({email, slackInvite, emailUpdates})
})
console.log(res)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The function does not return a response. Netlify Functions must return an object with statusCode, headers, and body properties, or the function will fail.

Fix by adding a return statement:

const res = await fetch('https://api.friggframework.org/subscribe', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json;charset=utf-8'
    },
    body: JSON.stringify({email, slackInvite, emailUpdates})
})
console.log(res)
return {
    statusCode: 200,
    body: JSON.stringify({ message: 'processed' })
}
Suggested change
exports.handler = async function (event, context, callback) {
const payload = JSON.parse(event.body).payload
console.log(JSON.parse(event.body))
console.log(payload)
console.log(payload.data["slack-invite"])
const email = payload.data.email
const slackInvite = !!payload.data["slack-invite"]
const emailUpdates = !!payload.data["update-emails"]
console.log(email, slackInvite, emailUpdates)
const res = await fetch('https://api.friggframework.org/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({email, slackInvite, emailUpdates})
})
console.log(res)
}
exports.handler = async function (event, context, callback) {
const payload = JSON.parse(event.body).payload
console.log(JSON.parse(event.body))
console.log(payload)
console.log(payload.data["slack-invite"])
const email = payload.data.email
const slackInvite = !!payload.data["slack-invite"]
const emailUpdates = !!payload.data["update-emails"]
console.log(email, slackInvite, emailUpdates)
const res = await fetch('https://api.friggframework.org/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
body: JSON.stringify({email, slackInvite, emailUpdates})
})
console.log(res)
return {
statusCode: 200,
body: JSON.stringify({ message: 'processed' })
}
}

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Already handled in the current head — submission-created.js ends with return { statusCode: 200, body: JSON.stringify({ message: 'processed' }) }, so it returns a valid Netlify Functions response object.


Generated by Claude Code

claude added 8 commits July 24, 2026 02:00
Content / POV:
- Lead with the strong opinion that an integration is more than moving data.
  New "primitives" section: ad hoc endpoints, queues, provider-native code
  (Attio, HubSpot, Zapier, Zendesk, Salesforce) with shipped helpers, and
  in-app UI via the emerging Fenestra spec.
- Add a "speaks the specs your tools know" strip: OpenAPI, AsyncAPI, Arazzo,
  Overlays, JSON Schema, MCP, and Fenestra (new), positioned as sitting
  alongside the standards rather than replacing them.
- Fix the use-cases intro that wrongly called Frigg "a framework for moving
  data between systems," which contradicted the new POV.
- Nav gains Primitives.

Voice (per Left Hook content style guide):
- Remove all em-dashes (was 29; now 0), using periods, commas, and
  parentheses instead.
- Lower the hype: reword the "backlog was a people problem / now it's an
  agent's job" headline toward judgment ("deciding which to build is still
  yours") and drop "the wheel builds itself" flourish.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
A spec-sheet section listing the real framework stack, pulled from the repo.
Where there's a genuine choice, it's rendered as an orange "or":
- Data store: PostgreSQL or MongoDB (both via Prisma ORM; Mongoose is no
  longer a declared dependency on next).
- Encryption: AWS KMS or AES-256 (field-level, bring your own keys).
- Cloud: AWS today, or GCP / Azure / self-hosted container.

Other rows: Node.js 22+ / JavaScript, AWS Lambda, Serverless Framework (osls)
+ esbuild, generated serverless.yml + CloudFormation, AWS SQS, EventBridge
Scheduler + cron events, SSM Parameter Store + Secrets Manager, Express via
serverless-http, OAuth2 / API key / Basic, JSON Schema (JSONForms) + React
admin UI, Jest / nock / in-memory DB, npm workspaces / Lerna / Nx, and the
hexagonal/DDD architecture. Nav gains Stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
Frigg natively supports OpenTelemetry (core ships @opentelemetry/api + SDK for
traces and metrics with OTLP HTTP exporters; api-module requests are wrapped in
telemetry spans). Add an Observability row to the stack spec sheet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
Embed an inline monochrome SVG sprite (Simple Icons, CC0) and render a brand
glyph inside each stack chip in currentColor, so logos are theme-safe and add
no external requests. Node.js, JavaScript, Serverless, esbuild, PostgreSQL,
MongoDB, Prisma, Express, React, Jest, OpenTelemetry, npm, Lerna, Nx, Google
Cloud, Docker, and JSON all use their marks; AWS-family chips (Lambda, SQS,
KMS, SSM, Secrets Manager, CloudFormation, EventBridge) and Azure use a neutral
cloud glyph since those marks are trademarked and unavailable. The Cloud row's
alternative is split into GCP / Azure / container chips so each gets its logo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
- subscribe.js: the Zapier webhook fetch used an invalid `contentType` option
  (silently ignored). Send a proper `Content-Type: application/json` header
  instead (flagged by Graphite).
- submission-created.js: return a 200 response object from the Netlify function
  handler instead of falling off the end (flagged by Graphite).
- index.html: replace decorative Math.random() (weave canvas + terminal timing,
  not a security context) with a small deterministic generator, clearing the
  SonarCloud PRNG findings that were failing the security quality gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
New /roadmap page built from real data on the next branch:
- Framework roadmap: all 27 ADRs (docs/architecture-decisions) as filterable
  cards (search, status All/Proposed/Shipped, theme), each linking to the ADR
  on GitHub. Data in website/roadmap/data/adrs.json.
- API module directory: the full 224-API Left Hook catalog, searchable by
  name/provider/tag with category + complexity filters. Data in
  website/roadmap/data/apis.json.
- Hybrid voting: live counts via Netlify Blobs (friggframework-api/vote.js +
  votes.js), one vote per browser (localStorage), plus a per-item "Request" /
  "Discuss" link to GitHub issues/discussions. Votes degrade gracefully to 0
  when functions aren't running (e.g. local static preview).
- Reuses the site's design system, fonts, and light/dark/system toggle.

Wire-up: homepage nav + footer + the get-started "See the roadmap" link now
point at /roadmap/ (previously roadmap.friggframework.org).

Data snapshots and the voting functions were produced by parallel subagents;
the page and integration were assembled against fixed contracts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
… roadmap

- Logos: pull the 213 matching brand logos from Left Hook's marketing assets
  (keyed by catalog slug) into website/roadmap/logos/ and render each in a
  white chip on the directory cards, with a monogram fallback for the few
  APIs that have no logo.
- Real library status: cross-reference the 224 catalog APIs against the actual
  friggframework/api-module-library packages (v1-ready + needs-updating). 35
  APIs that ship a module now get a truthful green "In the library" badge, plus
  a new All / In library / Candidates filter. Non-built APIs keep an effort
  badge (Low/Medium/High).
- Category grouping: the directory now groups cards under category headers with
  per-category counts (largest first); the category dropdown narrows to one
  group. Filter and grouping in one.

Data (apis.json) gains logo, built, and library fields.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq
A small concierge that answers questions about the framework, helps sketch
an integration, and acts as a roadmap guide, with helpful Left Hook context.

- website/friggframework-api/assistant.js: Netlify function on the AI Gateway
  (@anthropic-ai/sdk auto-reads ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL). Grounded
  system prompt covering the framework, stack, primitives, roadmap themes, and
  Left Hook services. Per-IP rolling rate limit via Netlify Blobs (fails open),
  input clamps, history trimming, and graceful offline degradation when the
  gateway isn't configured. Model is env-overridable (ASSISTANT_MODEL); defaults
  to opus. A FREYA-SEAM marks where a Freya session will later drive the loop.
- Chat widget on the homepage and roadmap page: launcher + panel, reuses the
  existing design tokens, dark/light aware, keyboard + reduced-motion friendly,
  full-screen on mobile. Model output is escaped before rendering; only links
  and code spans are formatted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0113LVpu5eMEkoCmx3zuCHiq

Copy link
Copy Markdown
Contributor Author

Session handoff — on-site "Ask Frigg" assistant + Freya seam

This note is the context bundle for a new session that will wire the assistant to the Freya framework. The Freya repo isn't in this session's scope and can't be added retroactively, so the follow-up work needs a fresh session with the Freya repo added. Everything below is what that session needs to pick up cleanly.

What exists now (this PR)

The marketing site was migrated into website/ and rebuilt (see the PR description). On top of that, this session added an on-site AI assistant — a chat widget ("Ask Frigg") on both the homepage and the roadmap page, backed by a Netlify function running on the Netlify AI Gateway.

It already works end to end as a single grounded Claude call. Freya is the next iteration: replace the one-shot call with a Freya-driven loop that retrieves ADR / API / catalog data at runtime via tools/MCP instead of relying on the baked-in static knowledge blob.

Files to know

File Role
website/friggframework-api/assistant.js The Netlify function. Contains the model call, the rate limiter, and the static knowledge blob. This is where Freya plugs in.
website/index.html Homepage. Widget markup + CSS + driver JS live near the end (search fa-launch, fa-panel, and the Assistant widget comment).
website/roadmap/index.html Roadmap page. Same widget (shared styles/behavior).
website/roadmap/data/adrs.json 27 ADRs (num, id, title, status, date, theme, summary, url) — generated this session from docs/architecture-decisions/ on next.
website/roadmap/data/apis.json 224 APIs (slug, name, provider, category, description, status, complexity, built, library, logo, tags) + a categories array.
website/package.json Added @anthropic-ai/sdk. @netlify/blobs already present (used by voting + rate limiter).

The Freya seam (the important part)

assistant.js is structured so the model interaction is isolated behind one function. Search the file for FREYA-SEAM. The contract to preserve:

  • HTTP: POST /.netlify/functions/assistant with body { messages: [{role:'user'|'assistant', content:string}, ...] } → returns { reply: string, offline?: boolean, rateLimited?: boolean, retryAfter?: number }. The widget drops the last user turn from history on offline/rateLimited so failures don't poison context.
  • Model boundary: async function answer(messages) takes the sanitized message array and returns the reply string. Today it's a single client.messages.create() over the AI Gateway (new Anthropic() auto-reads ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL that Netlify injects). Swap the body of answer() for a Freya session and keep the (messages) => string shape — the handler, rate limiter, offline path, and both widgets need no changes.

Direction for the Freya version: retrieve, don't hardcode

Right now grounding is a curated static string (const KNOWLEDGE in assistant.js) plus a system prompt that tells the model to send people to /roadmap/ rather than recite lists. That was deliberate for a one-shot call, but the Freya assistant should fetch ADR / API / catalog info through data-retrieval tools/MCP at request time so answers stay accurate as the framework moves. Sources available:

  • ADRs: authoritative source is docs/architecture-decisions/ on the next branch (27 ADRs). A pre-summarized copy is already served at /roadmap/data/adrs.json, which is the cheapest retrieval target for the assistant.
  • API catalog: /roadmap/data/apis.json (224 APIs with the built/library flags that mark what's in api-module-library), or the Left Hook catalog MCP used this session, which exposes richer tools — list_apis, search_catalog, catalog_query, get_api, get_catalog_stats, compare_platforms, list_platforms, and get_lefthook_services (the last is where the Left Hook services/partnership grounding came from).
  • Framework facts / stack: currently inline in KNOWLEDGE; fine to keep a small static core, but let Freya's tools override anything that can drift (versions, module availability, ADR status).

So the target architecture: small static system prompt (voice + rules + framework primitives) + Freya tool calls for anything that changes. Retire most of the KNOWLEDGE blob as tools replace it.

Config the new session should preserve

  • ASSISTANT_MODEL env var selects the model. Default in code is claude-opus-4-8. For a public widget on gateway credits, set it to claude-haiku-4-5-20251001 (recommended) or claude-sonnet-4-5-20250929 in the Netlify env — no code change needed. (Open question for @seanspeaks: flip the code default to Haiku, or keep opus + env override?)
  • Rate limiting: rolling per-IP window via Netlify Blobs (assistant-rate store), 12 req/min, MAX_MESSAGES/MAX_CHARS clamps, fails open if Blobs is unavailable. Keep this in front of any Freya loop — a tool-using agent is more expensive per turn, so the limiter matters more, not less.
  • Graceful offline: if ANTHROPIC_API_KEY/ANTHROPIC_BASE_URL are absent or the call throws, the function returns a friendly offline reply (200, offline:true) pointing at docs/repo/roadmap. Preserve this so local dev and gateway outages don't 500.
  • XSS: widget escapes all model output before rendering; only URLs, /roadmap/ paths, and `code` spans are formatted. If Freya returns richer content (citations, cards), extend the renderer carefully — don't inject raw HTML.

Steps for the new session

  1. Start a fresh session and add the Freya repo to scope (list_reposadd_repo), plus this repo (friggframework/frigg) on branch claude/migrate-static-website-frigg-w5i6ub.
  2. Read website/friggframework-api/assistant.js and find FREYA-SEAM.
  3. Replace the body of answer(messages) with a Freya session; wire Freya tools to the retrieval sources above. Keep the handler contract, rate limiter, and offline path.
  4. Trim KNOWLEDGE down to the static core as tools take over.
  5. Test the offline path, rate-limit path, and a live tool-using exchange; re-screenshot the widget; commit to the same branch.

Still outstanding (deploy, not code)

  • Merge this PR into next.
  • Re-point the friggframework-org Netlify project to build from friggframework/frigg (base website) so continuous deploy works — a UI/OAuth step. The assistant only goes live once the site is deployed with the AI Gateway enabled (env vars injected); until then it shows the offline note.

Generated by Claude Code

claude added 3 commits July 24, 2026 03:18
Color: the accent was a made-up emerald (#2f8f68). Retune it to Frigg's
actual brand green — the sage #71a087 from the Frigg logo/favicon, with
#2c704f for emphasis and a lifted #84b39b for dark ground. Left Hook's
orange (#ff5e00 / --dawn) stays a subtle secondary accent. Applied across
the homepage and roadmap token blocks plus the incidental terminal/glow
references.

Bot: rename the on-site assistant from "Ask Frigg" to "Ask Freya" (label,
title, aria-label, greeting on both pages) and give the server-side
persona the name Freya, setting up the Freya-powered iteration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ixfrejnfZWd8TPZamZYdv
Replace the one-shot Anthropic Messages call at the FREYA-SEAM with the Freya
agent framework. Freya (@freyaframework/*) is an unpublished, ESM,
pnpm-workspace monorepo, so it's vendored as a prebuilt self-contained bundle
rather than an install-time dependency.

- website/tools/freya-vendor/: bundling seed (entry.mjs) + esbuild driver
  (build.mjs) + README. entry.mjs wires a minimal runtime — AnthropicLLM over
  the AI Gateway, in-memory memory/session/ontology adapters, FakeEmbedding —
  and a small roadmap-retrieval tool surface (catalog_stats, search_adrs,
  search_apis) over the committed ADR/API catalog. Exports
  runTurn({systemPrompt, model, messages, data}) -> string.
- website/friggframework-api/lib/freya-runtime.mjs: the generated bundle
  (deep-imports keep transformers-js embeddings and Postgres out of the graph;
  their optional deps stay external behind lazy imports never hit here).
- assistant.js: answer() now dynamic-imports the bundle and drives a tool-using
  turn, seeding a session from the widget's history. The HTTP contract, rate
  limiter, and offline path are unchanged. KNOWLEDGE trimmed where the tools now
  provide live catalog/ADR facts; system prompt steers the agent to its tools.
- netlify.toml: ship the bundle + roadmap JSON via included_files.

An empty ontology keeps memory capture a no-op, so a turn stays one Anthropic
call plus tool iterations. Verified end to end against a mock gateway: offline
path (200/offline), 405 on non-POST, and a full tool loop (tool_use -> tool
result -> grounded answer) through the real handler.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ixfrejnfZWd8TPZamZYdv
Replace the empty placeholder ontology with a typed Frigg domain model
(Platform, ApiModule, Integration, Primitive, Capability, Adr, Visitor) with
relationships and enum vocabulary. Freya renders it into the system prompt each
turn, so the agent reasons in Frigg's real vocabulary and its tool arguments
(category / status / complexity) line up with the catalog.

Prompt-only grounding for now; the same typed entities make Freya's memory
capture meaningful once the site has a durable memory store. Rebuilt the
vendored bundle; verified the ontology renders into the system prompt and the
offline / 405 / live tool-loop paths still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ixfrejnfZWd8TPZamZYdv
if (m.content) {
contentBlocks.push({ type: "text", text: m.content });
}
const toolUseId = m.metadata?.toolUseId || m.toolInvocations[0].toolName + "_" + Math.random().toString(36).slice(2);
The generated website/friggframework-api/lib/freya-runtime.mjs is a prebuilt
esbuild artifact of the Freya framework, not hand-authored source. SonarCloud
was flagging it (e.g. Math.random for a fallback tool-use id — a false positive
in a non-security context). Add a .sonarcloud.properties (the Automatic Analysis
config file, which does not disable automatic analysis the way a
sonar-project.properties would) excluding the vendored bundle from issues and
duplication analysis.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ixfrejnfZWd8TPZamZYdv
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3.5% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@seanspeaks
seanspeaks merged commit 96d9e2d into next Jul 24, 2026
6 of 9 checks passed
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.

3 participants