Commit Graph
18 Commits
Author SHA1 Message Date
Patrick Erichsen dfca6f5d9d feat: add disposable Vercel and Convex PR previews (#3017)
Adds isolated Convex-backed Vercel PR previews with shared local/preview seeding, preview-safe routing, and production guards.
2026-07-08 20:54:09 -07:00
Jason (Json)andClaude Fable 5 2651570578 feat: promotions — runtime-fetchable promotional offers (#2961)
* feat: add promotions — runtime-fetchable promotional offers

Adds a standalone promotions entity so time-boxed promotional offers
can be created, activated, and expired at runtime without shipping a
CLI release.

- promotions table: slug, display fields, draft/active/ended status,
  time window, and a declarative CLI activation payload (provider,
  authChoiceId, plugin names, model refs, signup/docs/launch URLs)
- public API: GET /api/v1/promotions (active, in-window only, cached)
  and GET /api/v1/promotions/{slug} (hides drafts and pre-launch
  activations; serves ended state)
- homepage: active promotions render as cards via a public
  promotions.listActive query; section hidden when none are live
- admin writes via HTTP (POST create / {slug}/update / {slug}/status)
  and Convex mutations, both admin-gated with audit log entries
- management dashboard: Promotions page (admin-only) to create, edit,
  and activate/end promotions
- clawhub-admin CLI: promotions list/create/update/set-status

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: rebuild promotions form with proper labeled field grid

Replace the management search-row markup with Input/Textarea/Label UI
components in a dedicated responsive form grid (custom classes — the
legacy global .grid rule collides with Tailwind's grid utility).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: reject slug changes on non-draft promotions

Activated promotion slugs are referenced by external links and CLI
claim provenance; renaming them would break both. Drafts can still be
renamed freely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: refresh promotions at lifecycle boundaries

* fix: preserve published promotion history

* fix: align promotion visibility boundaries

* fix: preserve promotion timestamp integrity

* fix: harden promotion editor rendering

* fix: vary promotion queries by current time

* fix: paginate promotion history safely

* fix: keep ended promotions terminal

* fix: share promotion discovery cache

* fix: bound active promotion reads

* fix: align active promotion limits

* feat: publish promotions as a hosted feed (clawhub-promotions)

Adds a third hosted feed so OpenClaw clients can discover active
promotions through the same immutable-snapshot pipeline as the plugin
and skills catalogs (ETag/304 revalidation, CDN cache headers), with a
client cache fully separate from update checks.

- packages/schema: promotionsFeed wire contract (schemaVersion 1,
  deterministic serialization, window validation)
- convex/promotionsFeed.ts: publishInternal builds the snapshot from
  active, launched promotions (same visibility rule as the public API)
  and upserts the catalogFeedPublications row
- event-driven republication: promotions.update/setStatus schedule an
  immediate republish plus runAt jobs at future window edges, so
  activation, kill-switch, launch, and expiry all land without waiting
  for a periodic publish
- GET /api/v1/feeds/promotions served through the shared feed handler;
  vercel rewrites for /v1/feeds/promotions and /feeds/promotions

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: hide pre-launch promotions on the slug endpoint regardless of status

A promotion activated and then killed before startsAt was publicly
readable by slug. Hide all non-draft promotions before their window
opens; ended promotions that did launch stay visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: publish promotions after expiry boundary

* fix: keep promotions feed publications fresh

* fix: initialize promotions feed safely

* fix: use deployable promotions function name

* fix: keep categorize dialog open while dismissing the categories dropdown

The categories dropdown was modal, which disables pointer events on the
rest of the page while open. The click that dismisses the dropdown then
targets <body>, which the parent Dialog treats as an outside interaction
and closes too — discarding unsaved category selections. Render the
dropdown non-modal so only it dismisses and Save keeps working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: let plugin owners edit categories and topics after they are set

The categorize entry point vanished once metadata existed, leaving
owners no way to change categories or topics. Keep a compact owner-only
Edit control in the taxonomy row that reopens the categorize dialog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore: remove unrelated taxonomy changes

* fix: keep canceled promotions private

* fix: reject expired promotion launches

* fix: harden promotion input boundaries

* feat: enforce CLI authoring contracts on promotion writes

The OpenClaw consumer rejects promotions whose modelRef, provider, or
authChoiceId violate its shell-safe identifier grammars, skips aliases
that are not typed identifiers, and refuses model refs outside the
declared provider prefix — so a promotion authored with, say, a spaced
alias published cleanly and then silently degraded at claim time.

Validate all of it at the write path instead: shell-safe modelRef and
identifier grammars, typed-identifier aliases, <provider>/ model-ref
prefix when a provider is declared, and npm-safe plugin names via the
registry's canonical grammar (scoped @scope/name allowed). Update the
management form hint/placeholder to teach the alias contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: format promotions test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 10:02:52 -07:00
Jesse Merhi 0a42a8c74c fix: harden script CSP
Remove the static Vercel CSP in favor of per-request nonce-based CSP headers from the TanStack Start server entry. Preserve theme SSR without an inline bootstrap script and keep local development CSP allowances scoped to localhost.
2026-06-24 21:41:54 +10:00
Vincent Koc 6aab4f9437 feat(catalog): publish verified organization skills feed 2026-06-23 17:04:26 +08:00
Vincent Koc 94f2f532c3 fix(catalog): version and harden hosted plugin feed (#2806) 2026-06-23 16:11:40 +08:00
Vincent Koc 72c5cdd864 feat(catalog): publish hosted OpenClaw plugin feed (#2805)
* feat(schema): define hosted catalog feed contract

* feat(catalog): publish cached hosted plugin feed

* feat(catalog): add feed publication and edge delivery

* fix(catalog): recheck live official publisher state

* fix(workflow): require main for catalog publication

* fix(catalog): harden publication inputs

* docs(catalog): document consumer rollout boundary
2026-06-23 15:41:24 +08:00
Jesse Merhi e5f3ba272b fix: restrict docs auth return origins to dev only
* fix: couple docs auth localhost returns to a local app origin

The /auth/docs broker POSTs the signed-in user's auth token to the
return_to origin, but the allowlist trusted http://localhost:4173 /
127.0.0.1:4173 unconditionally, so production could hand the token to a
local listener.

Allow loopback return origins only when the app itself is served from a
loopback origin, so a public deployment (incl. staging/preview) can never
post the token to localhost regardless of runtime env. Keep the fixed
production docs origins (clawhub.ai, documentation.openclaw.ai,
docs.openclaw.ai), drop loopback from the production CSP form-action, and
record the token-destination invariant in specs/auth-identity.md.

* fix: align docs auth form destinations

* fix: retry public GitHub package fetches

* Revert "fix: retry public GitHub package fetches"

This reverts commit 1529aa34f3.
2026-06-15 21:52:06 +10:00
Patrick Erichsen fdb4d312f4 docs: restore OpenClaw-hosted ClawHub docs (#2631)
* Revert "fix: serve docs subdomain root (#2630)"

This reverts commit 9ef33726b8.

* Revert "feat: move docs to canonical subdomain"

This reverts commit 6afb91b772.

* Revert "docs: serve ClawHub docs with shared OpenClaw renderer (#2609)"

This reverts commit 71156751ff.

* fix: restore ClawHub email docs host

* test: preserve non-docs validation fixes

* test: remove stale docs proxy test

* docs: keep ClawHub contributor references current

* fix: keep docs auth callback outside docs routes
2026-06-14 16:05:34 -07:00
Patrick Erichsen 9ef33726b8 fix: serve docs subdomain root (#2630) 2026-06-14 13:28:45 -07:00
Peter Steinberger 6afb91b772 feat: move docs to canonical subdomain 2026-06-13 15:38:43 -07:00
Patrick Erichsen 71156751ff docs: serve ClawHub docs with shared OpenClaw renderer (#2609)
* feat: serve clawhub docs under docs route

* feat: build ClawHub docs with openclaw docs

* chore: remove unused nav icon export

* docs: use ClawHub logo in docs shell

* docs: prepare ClawHub docs for Mintlify

* test: use static preview readiness for playwright

* chore: ignore current esbuild audit advisories

* docs: build ClawHub docs with shared OpenClaw renderer

* test: print package publish e2e failures
2026-06-12 19:02:44 -07:00
Peter Steinberger e8deec13a2 fix: allow docs host auth callback form posts 2026-05-06 22:16:00 +01:00
Peter Steinberger bec9362361 feat: add ClawHub docs auth broker 2026-05-06 21:08:39 +01:00
Vincent Koc 65d02e57b0 fix(web): add frontend security headers 2026-04-30 03:04:20 -07:00
Patrick ErichsenandClaude Opus 4.7 4fe275eb50 fix(security): enable safe SVG handling so shields.io badges render
vercel.json currently allow-lists SVG-only hosts (img.shields.io,
shields.io, badgen.net, flat.badgen.net) while dangerouslyAllowSVG:
false rejects every SVG source. Those two settings are incompatible,
and every badge in every README on production is returning 400
INVALID_IMAGE_OPTIMIZE_REQUEST (e.g. the license badge on
/plugins/@opik/opik-openclaw).

Switch to the pattern Vercel documents for safely serving SVGs in
their NEXTJS_SAFE_SVG_IMAGES conformance rule:

- dangerouslyAllowSVG: true  — lets the optimizer accept SVG inputs
- contentDispositionType: attachment  — forces download instead of
  inline document rendering if someone navigates directly to the
  /_vercel/image URL (the only context where SVG scripts would run)
- contentSecurityPolicy: script-src 'none'; sandbox;  — blocks script
  execution in the response

Defense in depth: browsers already sandbox SVGs loaded through <img>
so scripts don't run there anyway; the CSP + attachment header cover
the edge case of someone opening the optimizer URL directly. Net
security is equivalent to rejecting SVGs, but badges actually render.

Docs: https://vercel.com/docs/conformance/rules/NEXTJS_SAFE_SVG_IMAGES

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:50:43 -07:00
Patrick ErichsenandClaude Opus 4.7 82ae30d940 fix(security): proxy README images via Vercel Image Optimization
Closes the XSS / IP-leak surface from rendering third-party README
images directly on clawhub.ai. Routes external http(s) <img> sources
through Vercel's /_vercel/image endpoint, which enforces a host
allow-list, rejects SVG by default, and re-encodes rasters to webp.

Docs: https://vercel.com/docs/image-optimization

- vercel.json: add `images` config — host allow-list (raw.githubusercontent,
  shields.io, etc., based on NuGet's published README allow-list),
  dangerouslyAllowSVG=false, formats=[webp], 1d minimum cache TTL.
- src/components/ProxiedImg.tsx: small wrapper that rewrites external
  http(s) src URLs to /_vercel/image?url=...&w=...&q=75. Local paths,
  relative paths, and data: URIs pass through unchanged.
- MarkdownPreview + SkillDetailTabs: pass ProxiedImg as the `img`
  component override to react-markdown — covers both raw HTML <img>
  and markdown ![](url) syntax.
- package.json: drop unused `next` dep (vestigial from staging merge,
  zero imports anywhere; doesn't affect next-themes).

Tests: 1028/1028 (was 1017, added 11 — ProxiedImg unit tests +
markdown integration tests covering proxied vs passthrough paths).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 22:12:40 -07:00
Peter Steinberger 914fa895c5 build: add China mirror redirect entrypoint 2026-03-21 19:08:17 -07:00
Peter Steinberger 3ddbac35f3 feat: proxy /api to Convex 2026-01-04 06:51:49 +01:00