From 65c6be977656ba3c554505bb2c6b984032b8b367 Mon Sep 17 00:00:00 2001 From: Rajat Date: Sat, 14 Nov 2026 10:08:46 +0530 Subject: [PATCH 1/2] docs: port Fumadocs docs to FrontLit docs folder Copy the apps/docs content into frontlit/docs as FrontLit Markdown with _sections.json. The old Fumadocs app is left in place. Co-Authored-By: Claude Opus 5.5 --- frontlit/docs/_sections.json | 10 + frontlit/docs/components/page-builder.md | 137 +++++++++++ frontlit/docs/components/text-editor.md | 133 +++++++++++ frontlit/docs/developers/authentication.md | 67 ++++++ frontlit/docs/developers/mcp.md | 131 +++++++++++ frontlit/docs/developers/overview.md | 29 +++ frontlit/docs/developers/pages-from-git.md | 161 +++++++++++++ frontlit/docs/getting-started/introduction.md | 14 ++ frontlit/docs/getting-started/overview.md | 74 ++++++ frontlit/docs/self-hosting/github-app.md | 213 ++++++++++++++++++ frontlit/docs/self-hosting/index.md | 189 ++++++++++++++++ frontlit/docs/teams/members.md | 112 +++++++++ 12 files changed, 1270 insertions(+) create mode 100644 frontlit/docs/_sections.json create mode 100644 frontlit/docs/components/page-builder.md create mode 100644 frontlit/docs/components/text-editor.md create mode 100644 frontlit/docs/developers/authentication.md create mode 100644 frontlit/docs/developers/mcp.md create mode 100644 frontlit/docs/developers/overview.md create mode 100644 frontlit/docs/developers/pages-from-git.md create mode 100644 frontlit/docs/getting-started/introduction.md create mode 100644 frontlit/docs/getting-started/overview.md create mode 100644 frontlit/docs/self-hosting/github-app.md create mode 100644 frontlit/docs/self-hosting/index.md create mode 100644 frontlit/docs/teams/members.md diff --git a/frontlit/docs/_sections.json b/frontlit/docs/_sections.json new file mode 100644 index 0000000..c7dba35 --- /dev/null +++ b/frontlit/docs/_sections.json @@ -0,0 +1,10 @@ +{ + "version": 1, + "sections": [ + { "folder": "getting-started", "name": "Getting Started", "order": 10 }, + { "folder": "teams", "name": "Teams", "order": 20 }, + { "folder": "self-hosting", "name": "Self hosting", "order": 30 }, + { "folder": "components", "name": "Components", "order": 40 }, + { "folder": "developers", "name": "Developers", "order": 50 } + ] +} diff --git a/frontlit/docs/components/page-builder.md b/frontlit/docs/components/page-builder.md new file mode 100644 index 0000000..21ab14a --- /dev/null +++ b/frontlit/docs/components/page-builder.md @@ -0,0 +1,137 @@ +--- +title: Page builder +description: Install and use the block-based WYSIWYG page builder and renderer. +nav_order: 10 +--- + +`@frontlit/page-builder` is a block-based page builder and renderer for React. It is self-contained — no backend +and no framework lock-in. Themes, blocks, and the WYSIWYG builder all run client-side. + +A page is a `Theme` plus a `WidgetInstance[]` layout. Each widget is one block from the registry (hero, header, +footer, pricing, faq, grid, rich-text, email-form, and more). + +Rich text editing and rendering live in [`@frontlit/text-editor`](/docs/components/text-editor), which this package +depends on and installs automatically. + +To try the builder, install the package and render `PageBuilder` as shown in [Usage](#usage), or run the [components demo](https://github.com/codelitdev/frontlit/tree/main/examples/components-demo) from the FrontLit repository. + +## Installation + +```bash +npm i @frontlit/page-builder +# or +pnpm add @frontlit/page-builder +``` + +Peer dependencies: `react` and `react-dom` (^18 or ^19). Next.js is **not** required. + +### CSS + +Import the compiled stylesheet. It includes the utilities, default tokens, and the theme-picker fonts: + +```ts +import "@frontlit/page-builder/styles.css"; +``` + +If the host already runs Tailwind CSS v4, you can skip `styles.css` and scan the packages instead. Import the +prose sheet so rich-text lists and code blocks survive Tailwind preflight: + +```css +@import "@frontlit/page-builder/theme.css"; +@import "@frontlit/text-editor/prose.css"; +@source "./node_modules/@frontlit/page-builder"; +@source "./node_modules/@frontlit/text-editor"; +``` + +## Usage + +Both `PageRenderer` and `PageBuilder` are client components (the package ships with a `"use client"` directive on +UI entries). + +```tsx +import { + PageRenderer, + PageBuilder, + createDefaultLayout, + getDefaultTheme, +} from "@frontlit/page-builder"; +import "@frontlit/page-builder/styles.css"; + +// Read-only rendering (a published page, a marketing site, a preview): +; + +// The interactive editor — persist however you like: + { + /* save */ + }} +/>; +``` + +`PageBuilder` holds all editor state in memory and calls `onChange` on every edit. The host app decides how (and +whether) to persist it. + +```tsx +import { PageBuilder, type PageBuilderState } from "@frontlit/page-builder"; + +function Editor({ savedPage }: { savedPage: SavedPage }) { + function handleChange(state: PageBuilderState) { + // Persist state.layout / state.theme / state.seo, e.g. via + // `updatePage(pageId, { layout: state.layout, ... })`. + } + + return ( + + ); +} +``` + +`header` and `footer` are required layout-level blocks. The builder repairs legacy or empty initial layouts by +adding them, keeps them at the outer edges, and never permits moving or deleting them. + +### Client-side routing + +Internal `/...` links render as native `` tags by default, so the package works in Vite, Remix, Astro, or any +other React host. Next.js (or any other router) can inject its own link: + +```tsx +import Link from "next/link"; +import { + PageRenderer, + type PageBuilderLinkProps, +} from "@frontlit/page-builder"; + +function AppLink(props: PageBuilderLinkProps) { + return ; +} + +; +``` + +`PageBuilder` accepts the same `linkComponent` prop and forwards it to the live preview. + +## What's inside + +| Subpath | What it exports | +| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | +| `@frontlit/page-builder` | Everything below, re-exported from one place | +| `@frontlit/page-builder/models` | `Theme`, `WidgetInstance`, `Widget`, `Page`, `Media`, etc. (re-exports `TextEditorContent` from `@frontlit/text-editor`) | +| `@frontlit/page-builder/primitives` | Themed UI atoms (`Button`, `Header1-4`, `Section`, `PageCard`, ...) + built-in themes | +| `@frontlit/page-builder/components` | Shared presentational bits: `Image`, `NavLink`, `VideoWithPreview` (re-exports `TextRenderer` from `@frontlit/text-editor`) | +| `@frontlit/page-builder/blocks` | The block library: Header, Hero, Content, Featured, Faq, Pricing, Banner, EmailForm, Footer, … | +| `@frontlit/page-builder/renderer` | `PageRenderer` — renders a page's `WidgetInstance[]` layout, with an `editing` mode for use inside the builder | +| `@frontlit/page-builder/builder` | `PageBuilder` — the full WYSIWYG editor (add/remove/reorder blocks, edit settings, switch themes) | +| `@frontlit/page-builder/data-slots` | Server-safe helper for hosts to resolve named data slots before rendering | + +`createDefaultLayout()` / `getDefaultTheme()` (exported from the root) build a ready-to-render starter landing +page. diff --git a/frontlit/docs/components/text-editor.md b/frontlit/docs/components/text-editor.md new file mode 100644 index 0000000..86b7d4f --- /dev/null +++ b/frontlit/docs/components/text-editor.md @@ -0,0 +1,133 @@ +--- +title: Text editor +description: Install and use the TipTap-based rich text editor and static renderer. +nav_order: 20 +--- + +`@frontlit/text-editor` is a TipTap-based rich text editor and static renderer for React. It is +framework-agnostic: it depends only on `react` / `react-dom`. No Next.js, no backend, and no media-upload +service. + +Uploaded images are embedded as data URLs by default. Hosts can replace that with their own picker via +`ImagePickerContextProvider`. + +The page builder's rich-text block uses this package. You can also use it on its own — for example in a +dashboard form, a comment field, or a help-center article editor. + +To try the editor, install the package and render `Editor` as shown in [Usage](#usage), or run the [components demo](https://github.com/codelitdev/frontlit/tree/main/examples/components-demo) from the FrontLit repository. + +## Installation + +```bash +npm i @frontlit/text-editor +# or +pnpm add @frontlit/text-editor +``` + +Peer dependencies: `react` and `react-dom` (^18 or ^19). + +### CSS + +Import the compiled stylesheet. It includes Tailwind utilities, fallback tokens for the editor chrome, +ProseMirror/renderer rules, and a small highlight.js theme. Tailwind is **not** required in the host: + +```ts +import "@frontlit/text-editor/styles.css"; +``` + +If you already run Tailwind CSS v4 and want to generate the utilities yourself, skip `styles.css`, scan the +package, and import the prose rules (lists, blockquotes, code blocks — these are not utilities): + +```css +@import "@frontlit/text-editor/tokens.css"; +@import "@frontlit/text-editor/prose.css"; +@source "./node_modules/@frontlit/text-editor"; +``` + +## Usage + +`Editor` is a client component. Persist the TipTap JSON it emits, then render that JSON with `TextRenderer`. + +```tsx +import { Editor, TextRenderer, emptyDoc } from "@frontlit/text-editor"; +import "@frontlit/text-editor/styles.css"; + + save(json)} />; +; +``` + +A typical host component: + +```tsx +"use client"; + +import { useState } from "react"; +import { + Editor, + TextRenderer, + emptyDoc, + type TextEditorContent, +} from "@frontlit/text-editor"; +import "@frontlit/text-editor/styles.css"; + +export function ArticleBody({ + initial, + onSave, +}: { + initial?: TextEditorContent; + onSave: (json: TextEditorContent) => void; +}) { + const [doc, setDoc] = useState(initial ?? emptyDoc); + + return ( + <> + { + setDoc(json); + onSave(json); + }} + /> + + + ); +} +``` + +### Custom image picker + +By default the editor embeds uploaded images as data URLs. Wrap the editor with +`ImagePickerContextProvider` to open your own media library instead: + +```tsx +import { Editor, ImagePickerContextProvider } from "@frontlit/text-editor"; + + { + const media = await openMediaLibrary(); + if (!media) return null; + return { src: media.url, alt: media.alt }; + }} +> + +; +``` + +### Theming the renderer + +`TextRenderer` accepts an optional, loosely-typed `theme` prop (`TextRendererTheme`) to style headings, +paragraphs, and links. Without one it falls back to plain semantic HTML tags. It is structurally typed against +that small theme shape instead of importing `@frontlit/page-builder`, so this package stays usable on its own. + +## What's inside + +- `Editor` — the WYSIWYG editing surface (toolbar, bubble menu, tables, code blocks with syntax highlighting, + images, links). +- `TextRenderer` — renders saved `TextEditorContent` (TipTap JSON) as static React elements. +- `TextEditorContent` — the TipTap doc-JSON type both of the above operate on. +- `normalizeTextEditorContent`, `extractTextFromTextEditorContent`, `truncateTextEditorContent` — content + utilities (e.g. for building excerpts). +- `emptyDoc`, `createExtensions`, `createId`, `extractHeadings`, `extractTextFromNode` — lower-level building + blocks, exported for advanced use. +- `ImagePickerContextProvider` — optional hook so a host can supply its own image-upload UI. diff --git a/frontlit/docs/developers/authentication.md b/frontlit/docs/developers/authentication.md new file mode 100644 index 0000000..9de8635 --- /dev/null +++ b/frontlit/docs/developers/authentication.md @@ -0,0 +1,67 @@ +--- +title: Authentication +description: Authenticating REST and MCP requests against the FrontLit API. +nav_order: 20 +--- + +The API recognizes three kinds of authentication, resolved in this order: + +1. **OAuth bearer token** — issued by FrontLit's OAuth authorization server for + a specific resource, client, user, and explicit scope set. A token belongs to + your account and works in every team you're a member of. +2. **Session cookie** — set by signing in through the web app (passwordless email OTP, via + [better-auth](https://www.better-auth.com)). Used by the dashboard itself; not applicable to server-to-server + callers. +3. **API key** — a team-scoped secret, sent as the `x-frontlit-apikey` header (or `apikey` body field). Create one + from **Team settings → API keys** in the dashboard, or via `POST /teams/:teamId/keys`. The full secret is only + returned once, at creation time — store it securely. + +```bash +curl https://api.frontlit.dev/pages \ + -H "x-frontlit-apikey: fl_live_..." +``` + +## Team scope + +Session and OAuth requests choose the team they act on with the `X-Frontlit-Team-Id` header (the dashboard sends +this automatically once you've picked a team). If an account belongs to exactly one team, the header can be +omitted. An API key always authenticates as exactly one, fixed team — no header needed, and a header naming another +team is rejected. + +```bash +curl https://api.frontlit.dev/pages \ + -H "Authorization: Bearer $TOKEN" \ + -H "X-Frontlit-Team-Id: team_..." +``` + +Use `GET /teams` to list the teams the token can reach. Team selection errors: + +| Error | Status | Meaning | +| --- | --- | --- | +| `team_required` | 409 | You belong to several teams and sent no header. The body lists your teams. | +| `no_team` | 409 | The account doesn't belong to any team yet. | +| `invalid_team_id` | 400 | No team has that ID. | +| `not_a_team_member` | 403 | You aren't a member of that team (or were removed). | +| `team_mismatch` | 403 | An API key was used with a team other than its own. | + +Access follows membership: a team you join later is reachable with the same token, and leaving a team stops access +on the next request. + +For a human member, team permissions and OAuth scopes are separate grants. +Browser requests require the member's current team permission. OAuth requests +require both that permission and the token's scope, so a client cannot exceed +the authority of the person who connected it. See [Team members and +permissions](/docs/teams/members) for the dashboard workflow. + +## MCP + +The remote MCP endpoint accepts an OAuth bearer token or +`x-frontlit-apikey`. It intentionally rejects dashboard session cookies. See +[MCP connections](/docs/developers/mcp) for protocol, client, scope, and +troubleshooting details. + +## Provisioning + +`POST /provisioning/teams` is a separate, server-to-server-only endpoint for multi-tenant consumers. It's guarded +by a shared secret (`X-Frontlit-Provisioning-Secret`), not an API key — see the [API Reference](/docs/api-reference) +for its request/response shape. diff --git a/frontlit/docs/developers/mcp.md b/frontlit/docs/developers/mcp.md new file mode 100644 index 0000000..a7c68b4 --- /dev/null +++ b/frontlit/docs/developers/mcp.md @@ -0,0 +1,131 @@ +--- +title: MCP connections +description: Connect OAuth and API-key clients to FrontLit's stateless MCP server. +nav_order: 30 +--- + +FrontLit exposes one remote MCP endpoint: + +```text +https://api.frontlit.app/mcp +``` + +The endpoint supports the stateless MCP `2026-07-28` protocol and older +Streamable HTTP clients through the official SDK 2 stateless compatibility +mode. FrontLit does not expose the retired standalone SSE transport: `GET +/mcp` is intentionally not a connection endpoint, and no client receives an +`Mcp-Session-Id`. + +## OAuth + +OAuth-capable clients should be given only the HTTPS `/mcp` URL. They discover +the protected resource at: + +```text +/.well-known/oauth-protected-resource/mcp +``` + +That document points to FrontLit's authorization server. Current clients can +onboard through a Client ID Metadata Document (CIMD); Dynamic Client +Registration remains available for clients such as MCP Inspector. Both use +authorization code with PKCE, resource binding, user consent, and explicit +tool scopes. + +An OAuth connection belongs to your account, not to one team. There is no +team picker: one connection works in every team you're a member of, including +teams you join later, and each call is limited to your role and permissions in +that team. Leaving a team stops the connection's access to it on the next call. + +Team-scoped tools take a `teamId` argument. Call `list_teams` to see your teams +with your permissions in each, then pass the chosen `teamId`. It is optional +only when you belong to a single team; otherwise the tool returns +`team_required` with the list of teams, and the assistant should ask you which +one to use. Results of tools that change something end with a line naming the +team they acted in. + +Tickets, contacts, documentation improvements, and GitHub proposals contain +text written by people outside your team. FrontLit marks those tools as +untrusted so assistants treat their text as data, not instructions. Review +what an assistant plans to change, especially when it has been reading +outside-written content. + +FrontLit configures an identity-only registration baseline (`openid profile +email`). Better Auth represents the combined identity and allowed MCP scopes +as a client's _capability set_, so a DCR response can list every MCP scope. +That is not an access grant: authorization requests with missing or blank +`scope` are rejected, and only explicitly requested and approved scopes are +issued in a token. A consent screen may therefore show many scopes because +the client requested them. + +Scope families include teams, API keys, pages, themes, media, blogs, help +documents, changelogs, contacts, segments, broadcasts, ESP configuration, +chatbot, helpdesk, domains, and settings. Read and write scopes are separate, +and sending a broadcast requires the distinct `broadcasts:send` scope. + +## API key + +A team API key is a full-team credential until scoped keys are introduced as a +separate product feature. Configure the client to send: + +```text +x-frontlit-apikey: fl_live_... +``` + +For example, clients that accept a JSON header configuration can use: + +```json +{ + "url": "https:///mcp", + "headers": { + "x-frontlit-apikey": "fl_live_..." + } +} +``` + +The submitted secret is verified at the HTTP boundary and is never copied +into MCP tool context or logs. API keys cannot call account-only tools such as +`list_teams`, `create_team`, or `delete_team`. A key only works in its own +team; a `teamId` naming any other team returns `team_mismatch`. + +## Local development + +Desktop and hosted clients commonly require public HTTPS. Start the API, then +use a controlled tunnel or Funnel that proxies the public hostname to the +local API port. Set `ENABLE_TRUST_PROXY=true` only when the deployment really +has the configured one trusted proxy hop. + +Check each layer before opening a GUI client: + +```bash +curl -i http://localhost:4000/health +curl -i http://localhost:4000/.well-known/oauth-protected-resource/mcp +curl -i http://localhost:4000/.well-known/oauth-authorization-server/api/auth +curl -i -X POST http://localhost:4000/mcp \ + -H 'content-type: application/json' \ + -H 'accept: application/json, text/event-stream' +``` + +The final request should return `401` with a `WWW-Authenticate` challenge that +contains the protected-resource metadata URL. Repeat the health and discovery +checks against the public hostname before starting OAuth. + +## Troubleshooting + +- If the browser completes OAuth but the client fails, inspect the first + subsequent `POST /mcp` status and body. +- Some clients try `GET /mcp` as standalone SSE after a failed POST. The + resulting `404 Cannot GET /mcp` is expected and is usually secondary to the + earlier POST failure; do not add SSE to hide it. +- `no_team` means authentication succeeded but the user has no team yet. + Create one with `create_team`, or join a team; no reconnect is needed. +- `team_required` means you belong to several teams and the call had no + `teamId`. Pass one from `list_teams`. +- `not_a_team_member` means the `teamId` names a team you don't belong to (or + were removed from). +- `invalid_client_metadata` involving Device Authorization or JWT Bearer means + the client's published CIMD grants changed or the exact compatibility rule + no longer matched. FrontLit does not globally relax grant validation. +- `ERR_ERL_UNEXPECTED_X_FORWARDED_FOR` means the proxy is forwarding client IPs + while Express is not configured to trust that deployment hop. +- Reconnect after changing scopes, discovery metadata, tool schemas, or the + tool catalog because clients may cache them. diff --git a/frontlit/docs/developers/overview.md b/frontlit/docs/developers/overview.md new file mode 100644 index 0000000..c8bec96 --- /dev/null +++ b/frontlit/docs/developers/overview.md @@ -0,0 +1,29 @@ +--- +title: Overview +description: The FrontLit REST/MCP API — teams, pages, themes, and provisioning. +nav_order: 10 +--- + +The FrontLit API is a ts-rest contract (`@frontlit/api-contract`) implemented over REST, documented via OpenAPI, +and mirrored 1:1 as MCP tools — the same contract and the same underlying queries back all three. + +## Concepts + +- **Team** — the tenant/data-scope boundary. Every page and theme belongs to exactly one team. An account can + belong to several teams. +- **Page** — a page built with `@frontlit/page-builder`: a `type`, a block `layout`, an optional saved `theme`, + and SEO metadata. +- **Theme** — a named, reusable set of colors/typography/structure settings, shareable across pages. +- **API key** — a team-scoped secret for server-to-server access (REST or MCP). Shown in full only once, at + creation time. +- **Provisioning** — a server-to-server endpoint (`POST /provisioning/teams`) for multi-tenant consumers to + find-or-create one FrontLit team per one of their own tenants, guarded by a shared secret rather than a normal + API key. + +See [Authentication](/docs/developers/authentication) for how to authenticate requests, and the +[API Reference](/docs/api-reference) for every endpoint. + +When running the API locally, the same contract is also available as Swagger +UI at [`http://localhost:4000/docs/`](http://localhost:4000/docs/) and +as raw OpenAPI JSON at +[`http://localhost:4000/openapi.json`](http://localhost:4000/openapi.json). diff --git a/frontlit/docs/developers/pages-from-git.md b/frontlit/docs/developers/pages-from-git.md new file mode 100644 index 0000000..cc9f3fd --- /dev/null +++ b/frontlit/docs/developers/pages-from-git.md @@ -0,0 +1,161 @@ +--- +title: Pages from Git +description: Keep landing pages in your repository as JSON files and publish them on every push. +nav_order: 40 +--- + +Your site's pages can live in your own repository, next to your product code. +Coding agents and teammates edit them in pull requests; FrontLit publishes the +folder on every push to your publishing branch. + +## Turn it on + +Connect GitHub in **Settings → Integrations**. On the folder step, choose the +**site folder** (default `frontlit`, or empty for the repository root) and tick +**Landing pages** (untick **Docs** if your docs live elsewhere). Then: + +- If `/pages` already has page files, + **Check repository** previews the routes, problems and any builder pages that + would go offline. **Finish setup** connects and publishes them in one step. +- If the folder doesn't exist yet, connect anyway, then open the repository + dialog and **Export pages**. That opens a pull request adding your current + pages, header, footer and theme to the folder. Merging it turns pages sync + on. Exported files re-import byte for byte. + +An already-connected repository can turn pages on later from the **Pages** +section of the same dialog. + +From then on every page and the site chrome are owned by the repository. In the +page builder, drafts stay editable and **Publish** becomes **Create pull request**. +**Stop syncing** hands pages back to the builder; what's live stays live. + +## Files + +Everything FrontLit syncs lives in one site folder, `frontlit/` by default: + +```text +frontlit/ +├─ site.json # header, footer, theme, redirects, agent instructions (optional) +├─ docs/ # Markdown docs → /docs/... +└─ pages/ + ├─ index.json # / + ├─ pricing.json # /pricing + └─ blog.json # /blog +``` + +- The file name is the URL: lowercase letters, numbers and hyphens. + `index.json` is the homepage. Folders inside `pages/` aren't supported yet. +- `site.json` sits at the root of the site folder. Without it, the current + header and footer stay. +- Some paths are reserved by the site (`/embed`, `/api`, `/schema`, …). + +A page file: + +```json +{ + "$schema": "https://frontlit.app/schema/page.v1.json", + "title": "Pricing – Acme", + "description": "Simple plans for every team.", + "layout": [ + { + "name": "hero", + "settings": { + "title": "Pricing that *scales* with you", + "description": "Start free, upgrade when you **grow**.", + "media": { "url": "../images/pricing.png", "alt": "Plans side by side" } + } + }, + { "name": "pricing" }, + { "name": "faq", "settings": { "items": [{ "question": "Can I cancel?", "answer": "Any time." }] } } + ] +} +``` + +- `layout` lists blocks top to bottom. A block's `settings` only needs what + differs from the block's defaults; the JSON Schema documents every setting and + its default. +- Rich-text settings are Markdown (CommonMark + GFM). +- Media is `{ "url", "alt" }`. The URL is an `https://` URL, or a path in the + repository (no scheme), relative to the file or to the repository root when + it starts with `/`; FrontLit uploads the image when it syncs. `http://` and + other schemes are rejected. +- `"published": false` keeps a page as a draft. `"robotsAllowed": false` asks + search engines not to index it. +- The blog, docs and changelog pages hold their feed with + `{ "name": "content-feed" }`. + +`site.json`: + +```json +{ + "$schema": "https://frontlit.app/schema/site.v1.json", + "header": { "links": [{ "label": "Pricing", "href": "/pricing" }], "ctaLabel": "Sign up", "ctaHref": "/signup" }, + "footer": { "tagline": "The front office of your SaaS." }, + "redirects": [{ "from": "/plans", "to": "/pricing" }], + "agentInstructions": "Use this site for questions about Acme's API and pricing." +} +``` + +`agentInstructions` is optional Markdown for AI agents: what the site is for +and when to use it. It shows as its own section of the site's `/llms.txt`. + +## Markdown for AI agents + +Every site also serves its public content as Markdown: + +- Request any page, docs page, blog post or the changelog with + `Accept: text/markdown`, or add `.md` to its URL (`/docs/setup.md`, + `/index.md` for the homepage). Browsers keep getting HTML. +- A missing page requested as Markdown returns a 404 with a short Markdown + body that links to `/llms.txt` and `/sitemap.xml`. +- `/llms.txt` lists the site's pages, docs, blog posts, changelog and OpenAPI + spec with links to their `.md` URLs. Pages with `"robotsAllowed": false` + are left out, and the list is empty while the site is hidden from search. + +## How a push is published + +- Each push to the publishing branch is published as one **release**: every + page plus the header, footer and theme, together with that commit's docs. +- Validation is all-or-nothing. If any file is invalid, or a header or footer + link points to a page that doesn't exist, nothing changes on the live site + and the problems (file, JSON pointer, message) show in the repository dialog. + Missing descriptions, long titles and broken links inside pages are warnings. +- A push that doesn't touch the pages folder, `site.json` or the docs folder + publishes nothing. +- **Website → Releases** lists releases; activate an older one to roll back. +- Pull requests that change pages get a **FrontLit / pages preview** check + linking to a private, unindexed preview of the result. + +## Formatting + +FrontLit writes files in one canonical form, so diffs stay small: keys in +schema order, settings equal to a default left out, 2-space indent and a +trailing newline. Files you write by hand don't have to match it. + +## For coding agents + +Install the `frontlit-pages` skill. It teaches agents the blocks, copy and SEO +rules and validates pages before they open a pull request: + +```bash +npx skills add codelitdev/frontlit-skills --skill frontlit-pages +``` + +Or add this to your repository's `AGENTS.md` (or `CLAUDE.md`): + +```md +## FrontLit pages + +Marketing pages live in `frontlit/pages/*.json` and publish on merge. +- File name = URL (`pricing.json` → `/pricing`, `index.json` → `/`). Flat folder only. +- Every file starts with `"$schema": "https://frontlit.app/schema/page.v1.json"`; + validate against it before committing. `frontlit/site.json` (schema + `site.v1.json`) holds the header, footer, theme, redirects and agent instructions. +- A page is `{ title, description, layout: [{ name, settings? }] }`. Only set + settings that differ from the block defaults listed in the schema. +- Rich text is Markdown. Media is `{ "url", "alt" }` with an `https://` URL or + a repo-relative image path; repo images are uploaded on sync. +- One H1 per page (the hero); other block titles are H2. +- Don't add a header or footer to pages — they come from `site.json`. +- Header and footer links must point at existing pages, or the release fails. +``` diff --git a/frontlit/docs/getting-started/introduction.md b/frontlit/docs/getting-started/introduction.md new file mode 100644 index 0000000..64d0fa6 --- /dev/null +++ b/frontlit/docs/getting-started/introduction.md @@ -0,0 +1,14 @@ +--- +title: Introduction +description: FrontLit is a block-based page builder and REST/MCP API for building and serving pages. +nav_order: 10 +--- + +FrontLit is the frontoffice of your SaaS: a block-based WYSIWYG page builder (`@frontlit/page-builder`) paired +with a REST/OpenAPI/MCP API for managing teams, pages and themes programmatically. + +- **[Getting Started](/docs/getting-started/overview)** — set up the API and web app locally. +- **[Self-hosting](/docs/self-hosting)** — run SendLit and its dependencies with Docker Compose. +- **[Components](/docs/components/page-builder)** — install and use `@frontlit/page-builder` and `@frontlit/text-editor`. +- **[Developers](/docs/developers/overview)** — authentication, teams, and API keys. +- **[API Reference](/docs/api-reference)** — the full REST API, generated from the same contract the server validates against. diff --git a/frontlit/docs/getting-started/overview.md b/frontlit/docs/getting-started/overview.md new file mode 100644 index 0000000..bd1d80f --- /dev/null +++ b/frontlit/docs/getting-started/overview.md @@ -0,0 +1,74 @@ +--- +title: Overview +description: Run the FrontLit API, dashboard, and public sites locally. +nav_order: 20 +--- + +FrontLit is a Bun workspace with four apps and several packages: + +- `apps/api` — the REST/OpenAPI/MCP API (Express, ts-rest, better-auth, Drizzle/Postgres). +- `apps/web` — the dashboard (Next.js), authenticated against `apps/api`. +- `apps/sites` — public tenant sites (Next.js). +- `apps/docs` — this documentation site (fumadocs). +- `packages/api-contract` — the shared ts-rest contract (Zod schemas + routes). +- `packages/page-builder` / `packages/text-editor` — the page builder and rich text editor. + +## Start local dependencies + +`docker-compose.yml` is the self-host product stack. For development, start +only Postgres, Redis, and SendLit: + +```bash +docker compose -f docker-compose.local.yml up +``` + +Postgres is on `127.0.0.1:5433`. SendLit is on `http://localhost:3001`. +Redis is on `127.0.0.1:6379` (used by SendLit inside Compose). The +`frontlit-migrate` job applies FrontLit schema to Postgres — wait for +`FrontLit migrations applied.` before starting the API. + +Do not run this at the same time as `docker compose up` (the self-host +stack) — both publish those ports. See [Self hosting](/docs/self-hosting) for +the in-Docker product. + +On first SendLit boot, copy the one-time organization key into +`apps/api/.env` as `SENDLIT_ORGANIZATION_API_KEY`: + +```bash +docker compose -f docker-compose.local.yml logs sendlit-api +``` + +## Run FrontLit on the host + +```bash +cp apps/api/.env.example apps/api/.env +cp apps/web/.env.example apps/web/.env +cp apps/sites/.env.example apps/sites/.env +# Point DB_CONNECTION_STRING at localhost:5433 (Compose publishes Postgres there). +bun run --filter @frontlit/api-contract build +``` + +Then start the apps in **separate terminals** (`&&` waits for each process +to exit): + +```bash +bun run dev:api +bun run dev:web +bun run dev:sites +``` + +Or run all three together: + +```bash +bun run dev +``` + +The API listens on `http://localhost:4000`. `GET /health` confirms it's up, +`GET /docs/` serves the interactive Swagger UI, and `GET /openapi.json` +serves the raw OpenAPI document. Private content authoring uses typed +resources below `/content`. + +The dashboard listens on `http://localhost:3000`. Signing in sends a +one-time code to your email (logged to the API's console in development +instead of actually emailed). Public sites listen on +`http://localhost:3002`. diff --git a/frontlit/docs/self-hosting/github-app.md b/frontlit/docs/self-hosting/github-app.md new file mode 100644 index 0000000..b00e63d --- /dev/null +++ b/frontlit/docs/self-hosting/github-app.md @@ -0,0 +1,213 @@ +--- +title: Set up the documentation GitHub App +description: Configure your own GitHub App, connect a documentation repository, and verify PRs and publication. +nav_order: 20 +--- + +Self-hosted FrontLit uses your own GitHub App to read documentation, open +setup and edit pull requests, and report validation and publication checks. +Create one App per deployment. Workspace administrators then connect their +repositories through **Settings → Content → Source**. + +You can use FrontLit's built-in documentation editor without GitHub. This +App is separate from the personal-token GitHub Releases integration. + +## 1. Choose your public URLs + +Use an HTTPS API origin reachable by GitHub and the administrator's browser. +For example: + +| Setting | Example | +| --- | --- | +| FrontLit dashboard | `https://app.example.com` | +| FrontLit API | `https://api.example.com` | +| Authorization callback URL | `https://api.example.com/integrations/github/callback` | +| Webhook URL | `https://api.example.com/integrations/github/webhook` | + +Both GitHub endpoints belong to the **API**, not the dashboard or public +sites service. Keep the callback URL identical in GitHub and FrontLit. + +For local development, use an HTTPS tunnel to the API's actual port. The +default API port is **4000**: + +```bash +curl http://localhost:4000/health +tailscale funnel 4000 +tailscale funnel status +``` + +Use the tunnel's HTTPS origin for the callback and webhook. Keep +`GITHUB_DOCS_WEB_ORIGIN=http://localhost:3000` for the local dashboard. +A tunnel forwarding to an unused port, such as 5000, produces a `502` before +FrontLit can process the callback. These commands are for local development; +a deployed reverse proxy should forward the same paths to the API. + +## 2. Register the App + +In the GitHub account or organization that will own the App, open +**Settings → Developer settings → GitHub Apps → New GitHub App**. + +1. Give the App a unique deployment-specific name and your FrontLit homepage. +2. Enter the authorization callback URL from step 1. +3. Enable **Request user authorization (OAuth) during installation**. Leave + expiring user access tokens enabled. Use the callback flow; leave the + separate Setup URL blank. +4. Activate webhooks, enter the webhook URL, enable SSL verification, and + set a randomly generated webhook secret. +5. Choose visibility that permits your intended repository owners to install + it. A private App is limited to its owning account; choose public when + other accounts need to install it. A public App can still be installed + on selected private repositories. +6. Configure permissions and events below, create the App, generate a client + secret, and generate/download a private key. + +See GitHub's [registration guide](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app). + +## 3. Set permissions **and** event subscriptions + +Under the App registration's **Permissions & events**, set these repository +permissions: + +| Repository permission | Access | FrontLit use | +| --- | --- | --- | +| Contents | Read and write | Read docs; create setup/edit branches and commits | +| Pull requests | Read and write | Open and track documentation PRs | +| Checks | Read and write | Report validation and publication outcomes | +| Metadata | Read-only | Identify the selected repository | + +Under **Subscribe to events**, select all three and save: + +| GitHub event label | Event name | FrontLit use | +| --- | --- | --- | +| Push | `push` | Reconcile the publishing branch after changes or merges | +| Pull request | `pull_request` | Validate new/updated PRs and track close/merge outcomes | +| Check run | `check_run` | Handle requested check reruns | + +**Permissions and subscriptions are separate.** Granting Pull requests write +does not enable Pull request events. With only Push selected, a new PR can +show no FrontLit check until independent PR discovery runs, normally on a +fifteen-minute schedule. That fallback does not prove webhook delivery. +Configure subscriptions on the developer-owned **App registration**, not +just the installation's repository-selection page. + +Do not grant Actions, Workflows, organization administration or branch-rule +bypass for this workflow. Permissions cover the selected repository; +FrontLit enforces the configured documentation directory within it. + +When you add repository permissions to an existing App, its installations +must approve the upgrade before those permissions take effect. Then review +the workspace's Git write permissions in FrontLit. See GitHub's +[App modification guide](https://docs.github.com/en/apps/maintaining-github-apps/modifying-a-github-app-registration). + +## 4. Configure FrontLit + +For Docker Compose, put these in the root `.env`. The API service's +`env_file` passes them to the container. For local Bun development, use +`apps/api/.env` instead. + +```dotenv +GITHUB_DOCS_APP_ID=your_numeric_app_id +GITHUB_DOCS_APP_SLUG=your-app-url-slug +GITHUB_DOCS_CLIENT_ID=your_app_client_id +GITHUB_DOCS_CLIENT_SECRET=your_generated_client_secret +GITHUB_DOCS_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nYOUR_KEY_CONTENT\n-----END RSA PRIVATE KEY-----\n" +GITHUB_DOCS_WEBHOOK_SECRET=your_webhook_secret +GITHUB_DOCS_CALLBACK_URL=https://api.example.com/integrations/github/callback +GITHUB_DOCS_WEB_ORIGIN=https://app.example.com +``` + +Copy the complete generated PEM key; the placeholder above is not a key. +Literal `\n` escapes in the environment value are supported. The App ID, +slug, client credentials and private key must all belong to the same App. +The webhook secret must match GitHub exactly. + +Keep these secrets on the API only, outside version control. Do not use +`NEXT_PUBLIC_*` variables. Set a dedicated `INTEGRATIONS_ENCRYPTION_KEY` +before storing setup credentials and retain it across restarts. Changing +that key later requires deliberate credential recovery. + +Recreate the API after changing Compose environment values: + +```bash +docker compose up -d frontlit-api +curl http://localhost:4000/health +``` + +For local development, restart your API process. Changing `.env` does not +update a running process automatically. + +## 5. Install and connect a repository + +Install the App on the repository-owning GitHub account. Prefer **Only +select repositories**, select the documentation repository, and save. +Existing installations also need to approve any pending permission upgrade. + +In FrontLit, select the intended workspace and open +**Settings → Content → Source → Authorize GitHub**. Sign in as a GitHub +repository administrator, authorize the App, and select its installation, +repository, publishing branch and documentation directory. + +Installation and user authorization are separate: having installed the App +does not by itself connect a FrontLit workspace. Setup authorization expires +after ten minutes. Use **Reconnect GitHub** if it expires; an already-created +setup PR remains available. + +For existing repository docs, review the proposed connection. For CMS +adoption or a new documentation site, review the captured files and choose +**Create setup PR**. FrontLit opens the PR; there is no manual export or +commit step. A genuinely empty repository needs an initial placeholder +commit, disclosed during review, before FrontLit can open its setup PR. +A missing branch in a nonempty repository must be corrected or replaced +with an existing branch. + +Review any unpublished-draft transfer disclosure: repository readers can +see draft text exported in the setup PR, including in a private repository. + +Merge the setup PR in GitHub. FrontLit tracks its outcome, verifies the +captured content, and presents **Finish setup** with the source/publication +approvals. If verification reports a mismatch, correct the files in GitHub +and use **Check files again**. Current public docs stay live until activation. + +## 6. Verify the complete workflow + +Use a disposable repository for the first smoke test: + +1. Complete source setup and confirm the live docs in the public site. +2. Save a doc edit in FrontLit, review the selected files, and submit a PR. + Confirm the PR was opened by your App and the public page is unchanged. +3. Confirm **FrontLit / docs validation** appears in GitHub and inspect + FrontLit's private **Git checks** report. +4. Merge the valid PR. Confirm a Push delivery succeeds in the App's + **Advanced → Recent Deliveries**, Source's live commit advances, and + **FrontLit / docs publication** reports the actual publication outcome. +5. Confirm the public page reflects the merged text. + +An edit is a local draft until submitted. A PR opens a review; it does not +publish. A merge triggers import and validation, then successful activation +publishes. A merged commit can fail documentation validation and leave the +last good publication live. FrontLit does not automatically merge PRs. + +For edited-document PRs, independent PR discovery runs on a fifteen-minute +schedule. Setup-PR outcome tracking normally polls about once a minute. +Webhooks provide the prompt path; missed-event reconciliation is a fallback. +App Contents write permission does not enable direct commits in a workspace; +that requires a separate explicit grant. + +## Troubleshooting + +| Symptom | What to check | +| --- | --- | +| Callback shows HTTP 502 | Confirm the API is healthy and the tunnel/proxy forwards to its actual port, default 4000. | +| Installation unavailable or authorization lists no repositories | Check App visibility, the signed-in GitHub account, organization approval, installation repository selection and suspension. | +| Setup expired or Authorize GitHub returns after a reload | Restart/reconnect the temporary setup authorization in the same FrontLit workspace; installing the App is not a permanent workspace setup credential. | +| PR creation cannot write code | Grant Contents write and Pull requests write on the App, approve the installation upgrade, then review FrontLit's Git write grant. | +| New PR has no validation check | Confirm Pull request subscription, Checks write, successful webhook deliveries and a healthy API worker. Push alone is insufficient. | +| GitHub rerun does nothing | Confirm the Check run subscription and inspect its webhook delivery. | +| Merge appears in GitHub but public docs are unchanged | Inspect Source's observed/validated/live commits and Git checks; an import may be pending or rejected. Refresh the setup PR status during initial setup. | +| `DUPLICATE_ROUTE` | Give published docs unique routes. The report identifies conflicting files; correct Git and let the next push reconcile. Last good docs remain live. | +| `UNSUPPORTED_NODE` on `.mdx` | Use safe Markdown. MDX components are not executed or silently imported. | + +Inspect delivery responses in the App's **Recent Deliveries** and redeliver +failed events after fixing connectivity or secrets. A successful API health +response alone does not prove callback, webhook or repository permissions. + diff --git a/frontlit/docs/self-hosting/index.md b/frontlit/docs/self-hosting/index.md new file mode 100644 index 0000000..dd6ad60 --- /dev/null +++ b/frontlit/docs/self-hosting/index.md @@ -0,0 +1,189 @@ +--- +title: FrontLit Self Hosting Guide +description: Run FrontLit, SendLit, and their dependencies from one Compose file. +nav_order: 10 +--- + +The root Compose file is the self-host stack. It runs: + +- PostgreSQL with separate `frontlit` and `sendlit` databases; +- Redis for SendLit's delivery and automation workers; +- the SendLit API at `http://localhost:3001`; +- the FrontLit API at `http://localhost:4000`; +- the FrontLit dashboard at `http://localhost:3000`; and +- public sites at `http://localhost:3002`. + +FrontLit still talks to SendLit only over REST (`SENDLIT_API_URL`). Inside +Compose that URL is the Docker network name `http://sendlit-api:3001`. Each +FrontLit team is provisioned as a dedicated SendLit team. + +## Prerequisites + +Install Docker Compose and have a checkout of FrontLit. + +For a public deployment, place the published HTTP ports behind an HTTPS +reverse proxy. Do not expose Postgres or Redis on a public interface. +Postgres is published on loopback (`127.0.0.1:5433`) so a host process can +still reach it; Redis stays on the Compose network. + +## Start the stack + +Create the Compose environment file: + +```bash +cp .env.example .env +``` + +Generate independent secrets, for example with `openssl rand -base64 48`, and +set these values in `.env`: + +```bash +BETTER_AUTH_SECRET= +PROVISIONING_SECRET= +SUPER_ADMIN_EMAIL=admin@example.com + +SENDLIT_BETTER_AUTH_SECRET= +SENDLIT_PIXEL_SIGNING_SECRET= +SENDLIT_ESP_CREDENTIALS_ENCRYPTION_KEY= +SENDLIT_SUPPRESSION_HASH_KEY= +SENDLIT_SUPER_ADMIN_EMAIL=admin@example.com +``` + +`SUPER_ADMIN_EMAIL` creates the initial FrontLit administrator. +`SENDLIT_SUPER_ADMIN_EMAIL` creates the initial SendLit administrator and +organization. + +Start everything and confirm health: + +```bash +docker compose up --build -d +curl http://localhost:4000/health +curl http://localhost:3001/health +``` + +The expected responses are JSON containing `"status":"ok"`. The first startup +creates both databases and runs migrations. These actions are idempotent, so +later `docker compose up -d` commands are safe. + +## Connect FrontLit to SendLit + +On its first boot, SendLit logs a one-time, scoped organization API key: + +```bash +docker compose logs sendlit-api +``` + +Find the `organizationApiKey` value. It has the `sl_org_live_` prefix and is +only shown when created. Set it in the root `.env`: + +```bash +SENDLIT_API_URL=http://sendlit-api:3001 +SENDLIT_ORGANIZATION_API_KEY=sl_org_live_... +``` + +Then recreate the FrontLit API so it reads the new value: + +```bash +docker compose up -d frontlit-api +``` + +New FrontLit teams will then be provisioned in the SendLit organization +automatically. The organization key is used only to provision and manage +those teams; FrontLit stores the separate, team-scoped SendLit key returned +for each provisioned team. + +`SUPER_ADMIN_EMAIL` likewise prints a FrontLit team API key once: + +```bash +docker compose logs frontlit-api +``` + +## Production configuration + +Use real domains and HTTPS before enabling production mode for SendLit: + +```bash +SENDLIT_NODE_ENV=production +SENDLIT_API_PUBLIC_URL=https://sendlit-api.example.com +SENDLIT_WEB_CLIENT=https://sendlit.example.com +SENDLIT_PROTOCOL=https +SENDLIT_DOMAIN=sendlit.example.com +``` + +And point FrontLit at the public origins: + +```bash +API_PUBLIC_URL=https://api.example.com +WEB_CLIENT=https://app.example.com +WEB_BASE_URL=https://app.example.com +SITES_BASE_URL=https://sites.example.com +PLATFORM_SITE_DOMAIN=example.com +ENABLE_TRUST_PROXY=true +``` + +Better Auth rejects HTTP login URLs in production mode. Keep the root `.env` +out of version control and use unique, high-entropy secrets for every +deployment. + +## Local Bun development + +To connect documentation to GitHub, follow +[Set up the documentation GitHub App](/docs/self-hosting/github-app). It covers +App registration, repository permissions, event subscriptions, callbacks, +webhooks and verification. + +Do not run `bun run dev:api`, `bun run dev:web`, or `bun run dev:sites` against +this file — `frontlit-api` / `frontlit-web` / `frontlit-sites` bind the +same ports. Use the local dependency stack instead: + +```bash +docker compose -f docker-compose.local.yml up +``` + +Then start FrontLit from the checkout. See +[Getting Started](/docs/getting-started/overview). + +## Operations + +```bash +docker compose ps +docker compose logs -f frontlit-api +docker compose logs -f sendlit-api +``` + +The `frontlit-postgres-data` and `sendlit-redis-data` Docker volumes persist +data across restarts. Back up the Postgres volume before upgrades or +destructive maintenance. Do not run `docker compose down -v` unless you +intend to delete all self-hosted FrontLit and SendLit data. + +## Troubleshooting + +### Email or Contacts says the team is not connected to SendLit + +The SendLit container being healthy is not enough. Each FrontLit team is +connected only after FrontLit provisions a matching SendLit team and stores +that team's API key. If `/email` or `/contacts` shows that the team is not +connected: + +1. Confirm the root `.env` has the **current** `SENDLIT_ORGANIZATION_API_KEY` + from `docker compose logs sendlit-api`. SendLit prints this key only when + it first creates the super-admin organization. A later SendLit reset + issues a new key; the old one returns `401 unauthorized`. +2. Recreate `frontlit-api` after changing that env var. The process reads + the environment at startup and will keep using a stale key until it + restarts. +3. Reset any exhausted provisioning retries. Failed `provision_sendlit` + outbox jobs stop after eight attempts and are not picked up again: + +```sql +UPDATE outbox_jobs +SET status = 'pending', + attempts = 0, + last_error = NULL, + next_attempt_at = NOW() +WHERE type = 'provision_sendlit' + AND status = 'failed'; +``` + +The API worker polls every few seconds. When the job succeeds, `integrations` +has a `sendlit` row for the team and the workspace pages show as connected. diff --git a/frontlit/docs/teams/members.md b/frontlit/docs/teams/members.md new file mode 100644 index 0000000..3ddbca1 --- /dev/null +++ b/frontlit/docs/teams/members.md @@ -0,0 +1,112 @@ +--- +title: Team members and permissions +description: Invite teammates and give each person only the access they need. +nav_order: 10 +--- + +Each FrontLit team has one **Owner** and any number of members. The owner can +use every team permission. Every other member receives an explicit set of +permissions; FrontLit does not use stored roles such as Admin or Editor. + +The same person can have different access in different teams. + +## Create or join your first team + +Signing in does not automatically create a team. If you are new to FrontLit, +the Account screen lets you create a workspace explicitly. If someone invited +you, open the link from the invitation email instead. Accepting it adds you to +that team and opens the first workspace area you are allowed to use; it does +not create a separate personal team. + +## Invite a teammate + +1. Select the team in the workspace switcher. +2. Open **Settings → Members**. +3. Choose **Invite**, enter the person's email address, then choose a preset or + a custom permission set. +4. Send the invitation. The recipient must sign in with that same email + address before they can review and accept it. + +Invitations expire after seven days. You can resend or revoke a pending +invitation from the Members screen. Resending creates a new link and makes the +previous one unusable. + +## Choose permissions + +Presets are shortcuts, not roles. Choosing one copies its permissions into the +member's access; you can then use **Custom** when a different combination is +needed. + +| Preset | Intended access | +| --------------- | ------------------------------------------------------------------------------------------------------------------- | +| Full access | All non-owner team permissions. It does not grant ownership or API-key management. | +| Content manager | Pages, themes, media, articles, documentation, and product updates. | +| Support | Inbox/help desk and documentation read access. | +| Marketing | Contacts, segments, and broadcast drafting. Sending a broadcast requires the separate `broadcasts:send` permission. | +| Read only | Read access across available product areas. | +| Custom | Select the exact permissions to grant. | + +Write permissions include the matching read permission. For example, +`pages:write` also lets a member view pages. Some permissions are marked as +sensitive in the picker because they can affect members, domains, AI settings, +general settings, or live broadcasts. + +## What members see + +FrontLit hides workspace areas a member cannot access. If someone follows an +old bookmark or switches to a team where their current page is unavailable, +FrontLit takes them to the first area they can access instead. This keeps the +workspace focused, but permissions are always enforced by the API as well. + +A member may be able to view a screen without having permission to change it. +Controls they cannot use are disabled or omitted. If permissions change, the +next request uses the new access immediately. + +For content libraries, read access lets a member browse existing items. Opening +an editor or creating, changing, publishing, or deleting content requires that +area's write permission. Direct editor links without write access return the +member to the readable library instead of showing an application error. + +Articles, documentation, and product updates appear under one **Content** +heading in the permission picker, but retain separate read/write pairs. This +lets a documentation writer work without also gaining authority to publish +articles or import product updates. + +## Manage members safely + +Members with **Members: invite** can invite people only with permissions they +already hold. Members with **Members: manage** can edit or remove only +non-owners whose access is no broader than their own. This prevents someone +from granting or changing authority above their level. + +The owner can edit or remove any non-owner. A member can leave a team at any +time. The owner must transfer ownership to an existing member before leaving +or deleting the team. Ownership transfer and team deletion require a fresh +email-code verification. + +## Owners and API keys + +The team owner is identified by the team itself, not by a permission preset. +Owners cannot be edited or removed through the member editor. Transfer +ownership when responsibility changes. + +Team API keys currently have full-team access, so only the owner can create, +list, or revoke them. Treat every API key as a sensitive integration secret. + +## OAuth scopes are separate + +Human permissions control what a signed-in person may do in a team. OAuth +scopes control what an external client may do for that person. For an OAuth +request to succeed, **both** the current team permission and the token's scope +must allow the action. Changing team permissions takes effect immediately, +even for a token that still has a matching scope. + +An OAuth connection belongs to a person's account and works in every team they +are a member of. When someone joins your team, the apps they've already +connected can act in it within their permissions; when they leave or are +removed, those apps lose access to your team on their next request. Changes a +connected app makes to members or API keys are recorded with its OAuth client +ID. + +See [Authentication](/docs/developers/authentication) for API keys, browser team +selection, and OAuth authentication details. From 12871bd02fe598a240449a2ab7af44d5223b1028 Mon Sep 17 00:00:00 2001 From: Rajat Date: Sat, 14 Nov 2026 10:10:35 +0530 Subject: [PATCH 2/2] docs: link API reference to the live Swagger UI FrontLit has no API reference configured yet, so /docs/api-reference links fail validation. Co-Authored-By: Claude Opus 5.5 --- frontlit/docs/developers/authentication.md | 2 +- frontlit/docs/developers/overview.md | 2 +- frontlit/docs/getting-started/introduction.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontlit/docs/developers/authentication.md b/frontlit/docs/developers/authentication.md index 9de8635..00d9703 100644 --- a/frontlit/docs/developers/authentication.md +++ b/frontlit/docs/developers/authentication.md @@ -63,5 +63,5 @@ troubleshooting details. ## Provisioning `POST /provisioning/teams` is a separate, server-to-server-only endpoint for multi-tenant consumers. It's guarded -by a shared secret (`X-Frontlit-Provisioning-Secret`), not an API key — see the [API Reference](/docs/api-reference) +by a shared secret (`X-Frontlit-Provisioning-Secret`), not an API key — see the [API Reference](https://api.frontlit.app/docs/) for its request/response shape. diff --git a/frontlit/docs/developers/overview.md b/frontlit/docs/developers/overview.md index c8bec96..e3fe4c3 100644 --- a/frontlit/docs/developers/overview.md +++ b/frontlit/docs/developers/overview.md @@ -21,7 +21,7 @@ and mirrored 1:1 as MCP tools — the same contract and the same underlying quer API key. See [Authentication](/docs/developers/authentication) for how to authenticate requests, and the -[API Reference](/docs/api-reference) for every endpoint. +[API Reference](https://api.frontlit.app/docs/) for every endpoint. When running the API locally, the same contract is also available as Swagger UI at [`http://localhost:4000/docs/`](http://localhost:4000/docs/) and diff --git a/frontlit/docs/getting-started/introduction.md b/frontlit/docs/getting-started/introduction.md index 64d0fa6..28cf8a1 100644 --- a/frontlit/docs/getting-started/introduction.md +++ b/frontlit/docs/getting-started/introduction.md @@ -11,4 +11,4 @@ with a REST/OpenAPI/MCP API for managing teams, pages and themes programmaticall - **[Self-hosting](/docs/self-hosting)** — run SendLit and its dependencies with Docker Compose. - **[Components](/docs/components/page-builder)** — install and use `@frontlit/page-builder` and `@frontlit/text-editor`. - **[Developers](/docs/developers/overview)** — authentication, teams, and API keys. -- **[API Reference](/docs/api-reference)** — the full REST API, generated from the same contract the server validates against. +- **[API Reference](https://api.frontlit.app/docs/)** — the full REST API, generated from the same contract the server validates against.