Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 60 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,63 @@ If the code and this file diverge, trust the code first and update this file.

## Project identity

`agent-render` is a fully static, zero-retention artifact viewer for AI-generated outputs.
`agent-render` is a fragment-first artifact viewer for AI-generated outputs.

It is meant to make markdown, code, diffs, CSV, and JSON readable across chat surfaces that do a poor job rendering rich artifacts inline.
Its main shipped product is still the fully static, zero-retention-by-host viewer for markdown, code, diffs, CSV, and JSON artifacts.

This repo also includes an optional self-hosted/server-backed deployment mode for power users and agents. That add-on stores the existing payload string in SQLite under a UUID v4 and serves `/{uuid}` links while reusing the same viewer UI.

Core product traits right now:
- open source
- publicly hostable
- self-hostable
- static-export friendly
- fragment-based transport so artifact contents stay out of the request URL and off the server request path
- static-export friendly for the main/default product
- fragment-based transport so artifact contents stay out of the request URL and off the server request path in static mode
- optional SQLite-backed UUID mode for server-backed deployments

## Product contract

Treat these as core constraints unless the owner explicitly changes the product direction.

- The app is a single exported client-side shell, not a backend product.
### Static fragment mode

- The main/default app is a single exported client-side shell, not a backend product.
- Artifact payloads live in the URL fragment, using `#agent-render=v1.<codec>.<payload>` for `plain|lz|deflate`, and `#agent-render=v1.arx.<dictVersion>.<payload>` for `arx`.
- The deployed host should not receive artifact contents as part of the initial page request.
- The deployed static host should not receive artifact contents as part of the initial page request.
- Supported artifact kinds are `markdown`, `code`, `diff`, `csv`, and `json`.
- Supported codecs are `plain`, `lz`, `deflate`, and `arx`.
- The product is zero-retention by host design, not secret-safe in an absolute sense.
- Links may still leak through browser history, copied URLs, screenshots, and any future client-side analytics.
- The product is zero-retention by host design in static mode, not secret-safe in an absolute sense.

### Optional self-hosted UUID mode

- It is a separate add-on deployment mode, not the default product.
- It stores the existing payload string as-is in SQLite under a UUID v4.
- It serves short links like `/{uuid}` and reuses the shared viewer.
- It uses a 24-hour sliding TTL.
- It is intentionally server-backed and not zero-retention.

Do not casually introduce:
- server persistence
- databases
- auth requirements for the core viewing path
- request-body upload flows for the main sharing workflow
- backend requirements for the main fragment-sharing workflow
- databases into the static/default path
- auth requirements for the core fragment viewer path
- request-body upload flows for the main fragment-sharing workflow
- normal query-param transport for artifact contents
- a second artifact schema for self-hosted mode when the existing payload string already works

## Current shipped behavior

Describe and preserve what is already true in the repo today.

### Shell and routing

- The app renders as one export-friendly shell.
- The default app renders as one export-friendly shell.
- The empty state explains the product and exposes sample fragment presets.
- A built-in link creator can generate fragment-based links locally in the browser.
- When a valid fragment is present, the app switches to a viewer-first artifact layout.
- The artifact stage toolbar exposes copy-to-clipboard, file download, and (for markdown) browser print-to-PDF.
- `activeArtifactId` controls which artifact opens first.
- Internal diff file navigation stays in UI state and does not repurpose the fragment.
- In self-hosted mode, `/{uuid}` injects the stored payload string into that same viewer shell instead of changing the renderer stack.

### Renderer behavior

Expand All @@ -65,6 +79,7 @@ Describe and preserve what is already true in the repo today.
- Heavy renderers are dynamically imported so the initial shell stays lighter.
- The diff stack remains the heaviest deferred renderer and is kept because the UX is worth it.
- On-demand language loading is preferred over bundling every language path up front.
- The self-hosted add-on reuses the exported viewer assets rather than shipping a second frontend.

## Security and safety posture

Expand All @@ -76,6 +91,7 @@ Rules:
- preserve sanitization on the markdown path
- fail clearly on malformed or oversized payloads before renderer mount when possible
- do not market the product as magically private beyond the actual host-retention boundary
- do not describe the self-hosted mode as zero-retention

## Payload protocol

Expand All @@ -91,6 +107,7 @@ Current rules:
- bundles must contain at least one artifact
- artifact ids must be unique within a bundle
- invalid `activeArtifactId` values normalize to the first artifact
- self-hosted mode stores that same payload string as the canonical DB value

Diff artifacts:
- prefer a real unified git patch in `patch`
Expand All @@ -102,7 +119,7 @@ If you change the payload contract, update the code, docs, examples, and the Ope
## Key files

### App shell and UI
- `src/components/viewer-shell.tsx` - main shell, fragment-driven state, empty state, artifact-stage layout
- `src/components/viewer-shell.tsx` - main shell, fragment-driven state, self-hosted bootstrap handling, empty state, artifact-stage layout
- `src/components/viewer/artifact-selector.tsx` - bundle artifact switching UI
- `src/components/viewer/fragment-details-disclosure.tsx` - fragment inspector/status disclosure
- `src/components/home/link-creator.tsx` - browser-side link creation UX
Expand All @@ -125,6 +142,15 @@ If you change the payload contract, update the code, docs, examples, and the Ope
- `src/lib/payload/link-creator.ts` - draft-to-link generation helpers
- `src/lib/payload/examples.ts` - sample envelopes and example fragments

### Optional self-hosted mode
- `src/lib/selfhosted/constants.ts` - UUID and TTL constants
- `src/lib/selfhosted/stored-payload.ts` - stored-payload normalization and validation helpers
- `src/lib/selfhosted/store.ts` - SQLite-backed payload store
- `src/lib/selfhosted/bootstrap.ts` - browser bootstrap payload reader
- `server/selfhosted-app.ts` - optional HTTP server, CRUD API, and UUID route handling
- `server/selfhosted.ts` - self-hosted runtime entrypoint
- `server/cleanup-selfhosted.ts` - manual cleanup command for expired rows

### Diff handling
- `src/lib/diff/git-patch.ts` - patch parsing support for diff rendering

Expand All @@ -136,6 +162,7 @@ If you change the payload contract, update the code, docs, examples, and the Ope
- `docs/dependency-notes.md`
- `docs/testing.md`
- `skills/agent-render-linking/SKILL.md`
- `skills/selfhosted-agent-render/SKILL.md`

## Development commands

Expand All @@ -153,6 +180,14 @@ npm run build
npm run preview
```

Optional self-hosted mode:

```bash
npm run build
npm run start:selfhosted
npm run cleanup:selfhosted
```

Subpath preview check:

```bash
Expand Down Expand Up @@ -182,21 +217,23 @@ npm run test:browsers
When making changes, preserve the product shape unless the owner explicitly wants a direction change.

### Do
- keep the core experience static-host friendly
- keep the core fragment experience static-host friendly
- add a preceding `/** ... */` block for public exported functions/components in `src/lib/**` and `src/components/**`
- keep the fragment transport client-side
- keep the fragment transport client-side for the main/default product
- prefer small, explicit protocol changes
- update docs when changing user-visible behavior or protocol semantics
- keep examples representative of the real supported envelope format
- verify visual changes with Playwright when they affect layout or renderer presentation
- review dependency licenses before introducing anything with unclear terms
- keep the self-hosted add-on simple: shared viewer, SQLite, UUIDs, practical docs

### Do not
- add backend requirements just to make sharing easier
- add backend requirements just to make fragment sharing easier
- move artifact bodies into normal query params
- promise unsupported artifact kinds in docs or UI copy
- leave the skill contract stale when the app contract changes
- describe roadmap ideas in this file as if they already ship
- weaken the static product’s zero-retention-by-host positioning just because the repo now also contains an optional server-backed mode

## Docs that must stay aligned

Expand All @@ -208,6 +245,7 @@ If any of these change, check the rest in the same pass:
- renderer capabilities
- local commands
- deployment assumptions
- self-hosted UUID behavior and TTL wording

At minimum, verify alignment across:
- `README.md`
Expand All @@ -217,16 +255,19 @@ At minimum, verify alignment across:
- `docs/dependency-notes.md`
- `docs/testing.md`
- `skills/agent-render-linking/SKILL.md`
- `skills/selfhosted-agent-render/SKILL.md`

## Default contributor stance

Be conservative with product claims and precise with protocol changes.

`agent-render` is useful because it is simple:
`agent-render` stays useful because it is simple:
- static
- linkable
- open
- self-hostable
- readable across chat platforms

Protect that simplicity.
And now, optionally, server-backed for short-lived UUID workflows.

Protect that simplicity.
121 changes: 110 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
# agent-render

`agent-render` is a fully static, zero-retention artifact viewer for AI-generated outputs.
`agent-render` is a fragment-first artifact viewer for AI-generated outputs.

Built for the OpenClaw ecosystem, `agent-render` focuses on fragment-based sharing for markdown, code, diffs, CSV, and JSON so the payload stays in the browser URL fragment instead of being sent to a server.
The main product remains the shipped static, zero-retention viewer: it renders markdown, code, diffs, CSV, and JSON directly from the URL fragment so the host does not receive the artifact body during the initial request.

This repo now also includes an **optional self-hosted mode** for power users and agent workflows. That add-on stores the existing `agent-render` payload string in SQLite under a UUID v4 and serves links like `https://host/{uuid}` while reusing the same viewer UI.

## Modes

### 1. Static fragment mode (main/default)

- fully static export
- zero-retention by host design
- no backend required
- payload lives in `#agent-render=...`
- best default for public sharing

### 2. Self-hosted UUID mode (optional add-on)

- separate server-backed deployment in the same repo
- stores the existing payload string as-is in SQLite
- serves `/{uuid}` viewer links
- 24-hour sliding TTL
- intended for local agents, private deployments, or links that are too large or awkward for fragments alone

The static fragment app is still the primary product. Self-hosted mode is an extra deployment option, not a replacement.

## OpenClaw

Expand All @@ -14,11 +36,12 @@ Built for the OpenClaw ecosystem, `agent-render` focuses on fragment-based shari

## Status

- Markdown, code, diff, CSV, and JSON all render in the static shell
- Markdown, code, diff, CSV, and JSON all render in the shared viewer shell
- Fragment transport supports `plain`, `lz`, `deflate`, and `arx`, with automatic shortest-fragment selection across packed/non-packed wire formats
- The `arx` substitution dictionary is served at `/arx-dictionary.json` so agents can fetch it for local compression
- The viewer toolbar copies artifact bodies to the clipboard, downloads them as files, and (for markdown) supports browser print-to-PDF
- Deployment target: static hosting, including Cloudflare Pages
- The viewer toolbar copies artifact bodies to the clipboard, downloads them as files, and supports browser print-to-PDF for markdown
- Static deployment target: static hosting, including Cloudflare Pages
- Optional self-hosted deployment target: Node.js service + SQLite

## Included Renderers

Expand All @@ -30,21 +53,30 @@ Built for the OpenClaw ecosystem, `agent-render` focuses on fragment-based shari

## Principles

### Static fragment mode

- Fully static export with Next.js App Router
- No backend, no database, no server-side persistence
- Fragment-based payloads (`#...`) so the server never receives artifact contents
- Public-safe naming and MIT-compatible dependencies

### Optional self-hosted mode

- Reuse the same viewer/renderers instead of introducing a second frontend
- Store the existing payload string without inventing a second artifact schema
- Keep server storage simple: SQLite, UUID v4 ids, create/read/delete, optional update
- Treat self-hosted mode as practical infrastructure for private or oversized sharing, not as a replacement for fragment links

## Local Development

```bash
npm install
npm run dev
```

## Local Preview
## Static Local Preview

For the real export-only runtime story:
For the export-first runtime story:

```bash
npm run build
Expand All @@ -53,6 +85,68 @@ npm run preview

Set `NEXT_PUBLIC_BASE_PATH` before `npm run build` when you want to preview a subpath deployment locally.

## Self-hosted Local Run

Build the shared viewer export, then start the optional SQLite-backed server:

```bash
npm run build
npm run start:selfhosted
```

Useful environment variables:

- `PORT` - HTTP port for the self-hosted service
- `HOST` - bind host, default `0.0.0.0`
- `AGENT_RENDER_DB_PATH` - SQLite file path, default `.data/agent-render-selfhosted.sqlite`
- `AGENT_RENDER_PUBLIC_ORIGIN` - external origin used when the API returns canonical UUID links

Cleanup expired rows manually when desired:

```bash
npm run cleanup:selfhosted
```

## Self-hosted API

The optional server-backed mode stores the existing payload string and exposes a simple JSON API:

- `POST /api/artifacts` - create a new stored payload
- `GET /api/artifacts/:id` - fetch and refresh TTL
- `PUT /api/artifacts/:id` - replace a stored payload and reset TTL
- `DELETE /api/artifacts/:id` - delete a stored payload
- `GET /:id` - render the stored payload through the shared viewer shell

Request body for create/update:

```json
{
"payload": "agent-render=v1.deflate.<payload>"
}
```

Stored payloads use a **24-hour sliding TTL**. Every successful read extends `expires_at` by another 24 hours. Expired rows fail clearly and are removed lazily on access or via the cleanup command.

## Deployment Notes

### Static mode

Deploy the generated `out/` directory to any static host.

### Self-hosted mode

Run `npm run build` first so the server can reuse the exported viewer assets from `out/`, then run `npm run start:selfhosted` behind whichever perimeter protection you prefer.

Reasonable patterns:

- same machine as the agent for local/private use
- Docker Compose with a mounted SQLite volume
- systemd or pm2-style process supervision
- Cloudflare Tunnel or another reverse proxy in front of the service
- optional Zero Trust or basic auth at the perimeter

Public exposure is possible if you want it, but it is not required by the app.

## Contributing

- Public exported functions/components in `src/lib/**` and `src/components/**` must have a preceding `/** ... */` JSDoc block.
Expand All @@ -63,8 +157,9 @@ Set `NEXT_PUBLIC_BASE_PATH` before `npm run build` when you want to preview a su

```bash
npm run lint
npm run test
npm run typecheck
NEXT_PUBLIC_BASE_PATH=/agent-render npm run build
npm run build
```

The home page includes sample fragment presets for every artifact type, including a malformed JSON case for error handling.
Expand All @@ -79,15 +174,19 @@ The shell keeps first load lean and defers renderer-heavy code until needed. The

- `docs/architecture.md` - architecture and tradeoffs
- `docs/payload-format.md` - fragment protocol, limits, and examples
- `docs/deployment.md` - deployment notes
- `docs/deployment.md` - deployment notes for both modes
- `docs/dependency-notes.md` - major dependency and license notes
- `docs/testing.md` - test commands, screenshot workflow, and CI notes
- `skills/agent-render-linking/SKILL.md` - fragment-first linking guidance
- `skills/selfhosted-agent-render/SKILL.md` - self-hosted UUID mode guidance

## Zero Retention

The project keeps artifact contents in the URL fragment so the static host does not receive the payload during the page request. This improves privacy for shared artifacts, but the link still lives in browser history, copied URLs, and any client-side telemetry you add later.
In static fragment mode, `Zero Data Retention by design` means the deployed host does not receive artifact contents as part of the initial request.

That does **not** mean the data disappears from browser history, copied links, screenshots, or any client-side analytics you add later.

`Zero Data Retention by design` means the deployed static host does not receive artifact contents as part of the request. It does not mean the data disappears from places like browser history, copied links, screenshots, or any client-side analytics you may add later.
Self-hosted UUID mode is different: it intentionally stores payload strings in SQLite for a limited time. Use it when that tradeoff is acceptable or desirable.

## License

Expand Down
Loading
Loading