diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ae908d..68ae8dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,5 +24,6 @@ jobs: - run: bash scripts/tests/check-docs-site.test.sh - run: bash scripts/tests/check-docs-content.test.sh - run: node scripts/tests/docs-ui.test.mjs + - run: node scripts/tests/docs-images.test.mjs - name: Validate the Mintlify build run: npm exec --yes --package=mint@4.2.876 -- mint validate diff --git a/AGENTS.md b/AGENTS.md index ecf1d30..382185c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,10 +11,20 @@ repository root. Preview and validation commands are in `README.md`. session or token internals, credentials, private API routes, infrastructure details, vendor names, or backend operator runbooks. Competitor product names never appear in copy, titles, or `docs.json`. -- The site carries **no images**. Every page has an `icon` in its frontmatter - and every `` has an `icon`; icons are Font Awesome names (the Mintlify - starter default, so `icons.library` stays unset). Do not add ``, - ``, markdown images, or an `image:` field. +- Product screenshots are welcome: use actual Cortex interface captures from + Paper, composed with Higgsfield wallpaper, not generated or invented UI. + Keep readable controls, honest feature states, and no private data. + Store exported WebP, PNG or JPG files locally in `images/product/`. + Use self-closing `...` + with descriptive, nonempty English alt text and intrinsic width and height. + `` wrappers are allowed. Put `className="block dark:hidden"` and + `className="hidden dark:block"` on enclosing `div` elements, only for actual + matching light/dark captures, so hidden zoom controls are not focusable. + Follow `design/docs-images.json`; never publish a `galleryOnly` preview. + No remote media, tracking pixels, data URLs, unsafe paths, dynamic image + attributes, markdown images or `image:` frontmatter. See README for examples. + Every page still has a frontmatter `icon` and every `` an `icon`; + icons are Font Awesome names (the starter default; `icons.library` stays unset). - Navigation is `navigation.tabs`: one tab per application (Get started, Chat, Code, Bot, CLI, Design, Security, Reference), each tab a list of groups, each tab and group with an icon. Every product tab opens on its hub page @@ -28,8 +38,11 @@ repository root. Preview and validation commands are in `README.md`. - Only document API paths on `reference/errors`, `bounty/public-api`, and the problem pages, and only paths the backend router registers. - Run `node scripts/check-docs-site.mjs`, + `node scripts/check-docs-content.mjs`, `bash scripts/tests/check-docs-site.test.sh`, - `node scripts/tests/docs-ui.test.mjs`, then + `bash scripts/tests/check-docs-content.test.sh`, + `node scripts/tests/docs-ui.test.mjs`, + `node scripts/tests/docs-images.test.mjs`, then `npm exec --yes --package=mint@4.2.876 -- mint validate` before committing. - For error-code or endpoint changes, also run the checker with a backend checkout as its first argument. Coordinate the two PRs; the backend owns the diff --git a/README.md b/README.md index f218d3d..bb4c8ba 100644 --- a/README.md +++ b/README.md @@ -57,15 +57,64 @@ otherwise collide (`Sessions` in Code and in the CLI, `Skills` in Chat, Bot, and the CLI) the title carries the product and `sidebarTitle` keeps the sidebar short. -The site has **no images**. Every visual is an icon (Font Awesome, the Mintlify -starter default). +Navigation cards and page frontmatter keep their Font Awesome icons (the +Mintlify starter default). Article bodies may also carry product screenshots. + +## Product screenshots + +Use actual Cortex interfaces captured from Paper and composed with Higgsfield +wallpaper. Do not generate or invent interface controls, imply an unavailable +feature is working, or expose personal data or credentials. Keep the interface +readable and place each screenshot beside the section it explains; reuse a +canonical view instead of repeating near-identical visuals across guides. + +Commit exports under `images/product/` as WebP, PNG or JPG (`.jpeg` also works). +Use root-relative URLs and literal, quoted attributes on self-closing `` +tags. Every image needs descriptive, nonempty English alt text. Include its +intrinsic width and height to reserve space while it loads. `` may wrap +an image and provide a caption: + +```mdx + + Cortex Chat home with its composer and workspace navigation. + +``` + +Only use theme switching when both files are actual matching captures: + +```mdx + +
+ Cortex Chat home with its composer and workspace navigation. +
+
+ Cortex Chat home with its composer and workspace navigation. +
+ +``` + +The checkers require existing, nonempty local files and reject path traversal, +symlink escapes, URL encoding, queries, fragments, remote URLs and data URLs. +Image attributes are limited to `src`, `alt`, `width`, `height`, `className`, +`loading`, `decoding` and `title`; expressions, spreads, event handlers and +`srcSet` are not supported. Markdown images, `image:` frontmatter and other +media embeds are not supported; use the checked `` form instead. +Examples inside code spans, fenced blocks and comments are not rendered images. +The shared syntax checks do not require a manifest. The provenance regression +test additionally checks this gallery against `design/docs-images.json`, rejects +`galleryOnly` assets, and catches unused public exports. See +[`design/docs-images.md`](design/docs-images.md) for the composition and +publication policy. ## Checks ```bash node scripts/check-docs-site.mjs +node scripts/check-docs-content.mjs bash scripts/tests/check-docs-site.test.sh +bash scripts/tests/check-docs-content.test.sh node scripts/tests/docs-ui.test.mjs +node scripts/tests/docs-images.test.mjs npm exec --yes --package=mint@4.2.876 -- mint validate ``` @@ -88,7 +137,8 @@ that adds the code. The full check fails when: - an `ErrorCode` is missing its `/problems/{code}` page, or a page documents a code the API does not emit - a `docs.json` navigation slug, group root, navbar or footer href, redirect destination, or internal link has no matching MDX page - a documented `/v1/…` path is not registered in `crates/cortex-api/src/router.rs` -- a page carries an image, lacks a title, description, or icon, repeats another page's title, or has a description over 160 characters +- a screenshot lacks alt text, references a missing or unsafe local file, uses an unsupported format or embeds remote media +- a page lacks a title, description, or icon, repeats another page's title, or has a description over 160 characters - navigation is not `navigation.tabs` with a tab for each of Chat, Code, Bot, CLI, Design, and Security, each tab and group carrying an icon - public MDX documents `/auth/`, `/oauth/`, `refresh_token`, the identity vendor, or the session cookie - `api/authentication.mdx` or `api/oauth.mdx` exist, or the navigation lists them diff --git a/bot/approvals.mdx b/bot/approvals.mdx index 75a9a96..4c0ea35 100644 --- a/bot/approvals.mdx +++ b/bot/approvals.mdx @@ -8,6 +8,10 @@ A bot can act on its own computer and in the apps you connected to it, so it ask This page covers the three choices, which tools always ask and which never do, the automatic policy that decides before you are involved, where pending approvals show up, and what happens when something cannot be settled. **Cortex Bot** is the only Cortex product with per-tool approval, because a bot runs without a round limit and with a machine of its own. + + The Bot approvals inbox with pending tool requests, command context, and allow or deny controls. + + ## The three choices One sentence from the product carries the whole model: `Allow runs the parked tool. Deny does not. Always writes a rule.` diff --git a/bot/computer.mdx b/bot/computer.mdx index ea9b461..c771af9 100644 --- a/bot/computer.mdx +++ b/bot/computer.mdx @@ -52,6 +52,10 @@ The desktop has one writer at a time. **Take control** hands the mouse and keybo Taking control is the right move when the bot is stuck on something only you can do, such as a screen it should not be clicking through on its own. For a password, a one-time code or a captcha, do not type it into the thread: the bot raises **Cortex needs you** and the value you enter there is delivered to the computer rather than posted as a message. + + The Bot workspace during a computer takeover, with the bot paused and a control for handing the desktop back. + + ## Record a session The **Videos** tab is the bot's **Recordings** list. A recording captures the full screen, the path the pointer took and zooms on its clicks, which makes it the clearest way to review what a routine did overnight. diff --git a/bot/conversations.mdx b/bot/conversations.mdx index 120d9e3..1675887 100644 --- a/bot/conversations.mdx +++ b/bot/conversations.mdx @@ -40,6 +40,10 @@ The absence of chatter is not the bot being stuck. To watch the detail of a job, Three things stop a turn and wait. All three appear in the thread, and the first two also block the composer until they are settled, so you cannot talk past a question the bot is holding. + + A Bot conversation waiting for help with a sign-in challenge, with an attention request and a take-over control. + + An `ask_user` call renders a question widget with **Reply** or **Confirm**, and short **Allow**, **Deny**, **Cancel** or **Dismiss** actions where the question is a yes or no. Answered questions are marked `Replied`; one you waved off is marked `dismissed`. diff --git a/bot/index.mdx b/bot/index.mdx index e2e4112..5346403 100644 --- a/bot/index.mdx +++ b/bot/index.mdx @@ -10,6 +10,10 @@ A bot is persistent. It keeps its name, its look, its memory, its skills, its ro This page is the map of the Bot documentation. Start with the quickstart if you have never made a bot, or go straight to the guide you need. + + A Bot workspace with an ongoing conversation, an approval request, and the bot's Cloud computer preview. + + ## Start here diff --git a/chat/attachments.mdx b/chat/attachments.mdx index 5899ce7..20b9bac 100644 --- a/chat/attachments.mdx +++ b/chat/attachments.mdx @@ -8,6 +8,10 @@ You can send a file along with a message in Cortex Chat. Pick it from your compu This page covers how to attach a file, what Chat accepts, the size ceiling and where it is enforced, what Cortex can read out of a document, and the messages you see when an upload or an extraction does not work. + + The Chat composer with file attachments and options for choosing files to send with a message. + + ## Attach a file diff --git a/chat/canvases.mdx b/chat/canvases.mdx index 8622d18..169cb78 100644 --- a/chat/canvases.mdx +++ b/chat/canvases.mdx @@ -8,6 +8,10 @@ A Chat canvas is a plain document that opens in a rail beside a conversation. Co This page covers how to open a canvas, how it saves itself, how to ask Cortex to rewrite part of it, how a canvas differs from a plan, a Library note, a Code file and a Design canvas, and what to do when the rail will not open or save. + + An editable document canvas beside a Chat conversation, with a passage selected for rewriting. + + ## Open a canvas diff --git a/chat/conversations.mdx b/chat/conversations.mdx index c4dba6a..b2c31f1 100644 --- a/chat/conversations.mdx +++ b/chat/conversations.mdx @@ -41,6 +41,10 @@ When standing instructions are in play, the composer shows **Custom instructions Nothing in a conversation is destroyed by changing your mind. Messages form a tree, and both controls below add a path rather than overwrite one. + + A Chat message being edited beside an answer version pager and message actions. + + Hover your own message and choose edit. The banner reads **Editing · original kept as Branch 1**. Change the text and press **Send as branch**, or **Cancel**. diff --git a/chat/deep-research.mdx b/chat/deep-research.mdx index 68be967..668f612 100644 --- a/chat/deep-research.mdx +++ b/chat/deep-research.mdx @@ -45,6 +45,10 @@ When a run was scoped, the report tells the reader: `Citations are shown only wh Progress is a short status line, not a wall of tool rows. **Research progress** moves through `Queued`, `Searching`, `Reading`, and then `Done` or `Failed`, and the line beside it uses a small fixed vocabulary: `Planning the research`, `Searching the web`, `Reading sources`, `Checking {topic}`, `Filling gaps`, `Writing the report`, `Checking connected apps`. The eyebrow above the block reads `Deep Research · Running`. + + An active Deep Research run showing progress across its research questions and the sources found. + + Two controls are yours while it works: - **Stop** ends the run. @@ -58,6 +62,15 @@ The finished report is ordinary Chat text with **numbered citations**, under the **Open reader** opens the report on its own, away from the thread, and **Follow up** takes you straight back into the chat with it in hand. **Close report** returns you to the conversation. + +
+ A completed Deep Research report in the reader with numbered citations, an outline, and a source sidebar. +
+
+ A completed Deep Research report in the reader with numbered citations, an outline, and a source sidebar. +
+ + Where a run cited nothing you see `No sources were cited.` or `No sources recorded for this report.` rather than an invented list. ## Save the report as a file diff --git a/chat/document-export.mdx b/chat/document-export.mdx index cfd2c68..0045072 100644 --- a/chat/document-export.mdx +++ b/chat/document-export.mdx @@ -12,6 +12,10 @@ This page covers the deck export a chat offers and the two ways into it, the pla From an ordinary answer, a deck is the only file a chat produces. Other document formats appear only on a Deep Research report card, where Markdown is the one implemented today. There is also no control anywhere in the app that exports a whole conversation as a document. + + A Chat answer beside a deck export panel with a slide outline to review before creating the file. + + ## Export happens in two steps Nothing is written until the second step, and the split matters for your allowance. diff --git a/chat/how-it-works.mdx b/chat/how-it-works.mdx index 13be48d..23eff71 100644 --- a/chat/how-it-works.mdx +++ b/chat/how-it-works.mdx @@ -8,6 +8,10 @@ A **turn** is one message from you plus everything Cortex does to answer it. Ins This page is for readers who want to know what is actually happening between pressing Enter and reading an answer: what goes into a turn, in what order, what is kept afterwards, and what happens when a conversation grows too long to send. + + A cited Chat answer with a footnote source panel open beside the conversation. + + ## What goes into a turn | Input | Where it comes from | diff --git a/chat/image-generation.mdx b/chat/image-generation.mdx index ae8d53b..c77809e 100644 --- a/chat/image-generation.mdx +++ b/chat/image-generation.mdx @@ -8,6 +8,10 @@ Ask **Cortex Chat** for a picture in plain language and it writes the picture pr This page covers asking for a picture, reading the card, steering the size and the seed, where the finished file lands, how many images each plan includes per day, and the cases where nothing runs at all. + + A generated image displayed as a result in a Cortex Chat conversation. + + ## Ask for a picture diff --git a/chat/index.mdx b/chat/index.mdx index 5ba09f8..b150ab6 100644 --- a/chat/index.mdx +++ b/chat/index.mdx @@ -8,6 +8,15 @@ Cortex Chat is the conversation product at [cortex.foundation](https://cortex.fo Chat works before you sign in, with a smaller allowance and without uploads, projects or web search. This page describes the surface you land on, then links to every other page in the Chat tab. + +
+ Cortex Chat home with a message composer, starting prompts, and workspace navigation. +
+
+ Cortex Chat home with a message composer, starting prompts, and workspace navigation. +
+ + ## The page you land on The home page greets you with **Good morning**, **Good afternoon** or **Good evening**, and shows a short list of starting points under the heading **Things Cortex does well.** diff --git a/chat/library.mdx b/chat/library.mdx index 5502eb8..521b14b 100644 --- a/chat/library.mdx +++ b/chat/library.mdx @@ -9,6 +9,15 @@ keywords: ["Artifacts"] This page covers how files arrive, how to find one, what the row actions do, how versions and **Restore** work, how pinning and the storage allowance behave, and how a file gets back into a chat. Sharing a file and connecting a drive have their own page, [Sharing files and connected drives](/chat/library-sharing). + +
+ The Library grid with document, deck, spreadsheet, and image previews alongside search and file-type filters. +
+
+ The Library grid with document, deck, spreadsheet, and image previews alongside search and file-type filters. +
+ + ## How files arrive | Source | Badge | What it is | diff --git a/chat/memory.mdx b/chat/memory.mdx index b3b71af..d556e94 100644 --- a/chat/memory.mdx +++ b/chat/memory.mdx @@ -8,6 +8,10 @@ icon: "lightbulb" Memory is on by default, and everything about it is inspectable. This page covers where to manage it, what it stores, how long it is kept, how to review and delete entries, how to narrow memory to one project, how export and import work, and the cases where memory deliberately does nothing. + + Chat memory settings with saved entries, search and category filters, and a memory being edited. + + ## Where to manage memory Everything below lives in **Settings → Data & privacy**, under **Memory**. The same panel carries the master switch, the sensitive-topics switch, the retention row, the searchable list of entries, and the export and import controls. The project-scope choice is the one part that lives elsewhere: it is inside each project, under its own **Memory** block. See [Projects](/chat/projects) and [Data and privacy](/getting-started/data-and-privacy). diff --git a/chat/models.mdx b/chat/models.mdx index 4f7a13e..f1a05aa 100644 --- a/chat/models.mdx +++ b/chat/models.mdx @@ -8,6 +8,10 @@ Every conversation in Cortex Chat runs on one model, and the chip under the comp This page covers the two models Chat publishes today, what the **PREVIEW** badge on them means, how the model and the thinking level are decided, and what Chat tells you when a model is busy or cannot be reached. + + The Cortex Chat model picker open above the composer with model choices and thinking controls. + + ## The two models The list in the panel is fetched from Cortex when the page loads rather than built into the app, so it always reflects what your account can actually use. Today it holds two entries, and both are marked Preview. diff --git a/chat/plugins.mdx b/chat/plugins.mdx index 3c05882..5da04e5 100644 --- a/chat/plugins.mdx +++ b/chat/plugins.mdx @@ -11,6 +11,10 @@ The heading on the page itself reads **Connectors**, and that is the name to loo This page covers what sits on the board, how to find an app, how connecting works, how to choose which products may use it, how often Cortex asks before an app makes a change, and how to disconnect. + + The connector catalogue with connected apps, connection statuses, search, and category filters. + + ## What is on the page The board is assembled in a fixed order: diff --git a/chat/projects.mdx b/chat/projects.mdx index d59a369..d895219 100644 --- a/chat/projects.mdx +++ b/chat/projects.mdx @@ -8,6 +8,10 @@ A **project** is where you keep a piece of work you come back to. It holds stand This page covers creating a project, giving it instructions and knowledge, filing an existing chat into it, choosing which memories its chats may use, handing its context to Cortex Bot or Cortex Code, and what a project cannot do yet. Projects need a signed-in account: the product says so plainly, because a guest session cannot be signed back into to recover them. + + A project workspace with its chats, message composer, standing instructions, and source summary. + + ## What a project holds | Part | What it does | @@ -37,6 +41,10 @@ Keep instructions short and factual. They are read before every reply, so they c Choose **Add a source** and pick one of **Upload from device**, **Add text content**, or **Add a link**. You can also drop files straight onto the project: the drop zone says PDFs, notes and other text become sources for every chat. + + A project's source list with files, links, notes, and the add-source menu beside its instructions. + + - **A link** is fetched by Cortex and stored as a source for the project. Cortex refreshes a link source once it is a day stale, so a page that changes does not go quietly out of date. - **Pasted text** needs a **Title** and a body. It is stored and listed as **Pasted text**. - **A file** can come from your device or from a file that is already in your [Library](/chat/library). Sources are chipped by kind: **PDF**, **Image**, **Code**, **Document**. diff --git a/chat/quickstart.mdx b/chat/quickstart.mdx index 7e5a380..50065bf 100644 --- a/chat/quickstart.mdx +++ b/chat/quickstart.mdx @@ -8,6 +8,15 @@ This walk-through takes a few minutes and starts from nothing. The first three s You will send a message, watch the answer being written, choose the model and how long it thinks, then sign in to attach a file and file the thread away. + +
+ A Cortex Chat conversation with a question, an answer, and the composer for a follow-up message. +
+
+ A Cortex Chat conversation with a question, an answer, and the composer for a follow-up message. +
+ + ## Your first conversation diff --git a/chat/scheduled-tasks.mdx b/chat/scheduled-tasks.mdx index 6c3039d..01f33e2 100644 --- a/chat/scheduled-tasks.mdx +++ b/chat/scheduled-tasks.mdx @@ -12,6 +12,10 @@ This page covers how to create a schedule from the Scheduled page or from a chat A scheduled run has no tools. It is one completion of your prompt: no web search, no Python, no connected apps, no memory writes, no file reads. If the answer needs any of those, run it yourself in the chat. + + The Chat Scheduled page listing tasks with status filters and a menu for running or pausing a schedule. + + ## Create a schedule diff --git a/chat/voice.mdx b/chat/voice.mdx index f60ff3c..206e7cd 100644 --- a/chat/voice.mdx +++ b/chat/voice.mdx @@ -8,6 +8,10 @@ Cortex Chat has two voice controls, and they are independent of each other. **Vo This page covers how each one behaves, what it does and does not send on your behalf, the limits that apply, and what it means when one of the controls is not there at all. + + Voice input in the Chat composer for dictating a message to review before sending. + + ## The two controls | Control | Where it is | What it does | diff --git a/code/approvals.mdx b/code/approvals.mdx index a771d69..3dec26e 100644 --- a/code/approvals.mdx +++ b/code/approvals.mdx @@ -38,6 +38,10 @@ Treat the three buttons as what the screen offers when a command is put to you, Writes and edits are deliberately excluded from the card above the transcript. They land in the **Changes** pane as pending files, and **until a human approves one, the tree is not written**. + + A Code session with pending file changes, a proposed diff, and controls to reject, request edits, or approve. + + Each file the agent touched is listed with its state: **Queued**, **Writing**, **Pending**, then **Written**. diff --git a/code/changes.mdx b/code/changes.mdx index 3e2bfcc..3d80599 100644 --- a/code/changes.mdx +++ b/code/changes.mdx @@ -8,6 +8,15 @@ When an Agent turn touches a file, the file does not quietly change. It appears This page covers the **Changes** tab and its per-file controls, the difference between the diff you read before a write and the diff you read after it, the live **Diff** tab, commenting on a line, and where the branch and the pull request show up. The command side of permissions lives on [Permissions and approvals](/code/approvals). + +
+ A Cortex Code session on macOS with an execution timeline beside a proposed code diff and approval controls. +
+
+ A Cortex Code session on macOS with an execution timeline beside a proposed code diff and approval controls. +
+ + ## The Changes tab The output pane on the right of a session carries the tabs **Changes**, **Diff**, **Git**, **Terminal** and, when there is one, **Plan**. **Changes** opens on the files this turn has touched, with a header count that reads **1 file changed** or **3 files changed** as the turn goes on. While the pane is reading the runtime it says **Loading changes…** diff --git a/code/how-it-works.mdx b/code/how-it-works.mdx index 9f06ed2..1c3d66f 100644 --- a/code/how-it-works.mdx +++ b/code/how-it-works.mdx @@ -21,6 +21,10 @@ A session binds one runtime, once per turn. There is no fallback. If the runtime Everything the agent does happens inside that runtime. Reads, searches, edits and shell commands all execute there. **Nothing runs in your browser**, and the browser never receives SSH keys or host keys. The browser offers Cloud, plus a registered team runner when the deployment has one; This PC and SSH belong to the desktop app and the CLI. See [Cloud runtimes](/code/runtimes) and [Code on desktop](/code/desktop). + + A Code session showing the agent's execution trace beside live terminal test output from its Cloud guest. + + ## The turn loop, step by step diff --git a/code/index.mdx b/code/index.mdx index 17fbc9f..2ffe380 100644 --- a/code/index.mdx +++ b/code/index.mdx @@ -23,6 +23,15 @@ Code is a member surface. A guest who visits the Code area sees the public produ Once you are signed in, Code Home is the composer itself. Its heading reads **What should Cortex build?** and its prompt box reads **Ask Cortex to build, fix, explore.** A guest can type there, but pressing **Start session** opens the sign-in dialog. That is an invitation, not an error. + +
+ The first-run Cortex Code home on macOS with a task composer, repository chooser, and GitHub connection prompt. +
+
+ The first-run Cortex Code home on macOS with a task composer, repository chooser, and GitHub connection prompt. +
+ + ## Start here diff --git a/code/quickstart.mdx b/code/quickstart.mdx index 470ae0f..c4752f1 100644 --- a/code/quickstart.mdx +++ b/code/quickstart.mdx @@ -8,6 +8,10 @@ This quickstart takes you from a signed-out browser to a first approved change, Everything here happens in the browser, where a session runs in an isolated Cloud guest. If you want a session to work on files on your own machine or on a server you own, that lives in the Cortex desktop app and the Cortex CLI instead; see [Code on desktop](/code/desktop). + + The new Code session dialog with repository, branch, interaction mode, model, and Cloud runtime choices. + + Open the Cortex app at [cortex.foundation](https://cortex.foundation) and sign in with your Cortex account, then switch to Code. diff --git a/code/sessions.mdx b/code/sessions.mdx index f80383f..c9ca178 100644 --- a/code/sessions.mdx +++ b/code/sessions.mdx @@ -22,6 +22,10 @@ This page covers the session as an object: how it is named, how to find one agai **Sessions** is the board of everything on your account. Its description line counts the sessions, the repositories they span and how many are running right now. + + The Code sessions list with repository and status filters for finding running, review, and failed sessions. + + | Control | What it does | | --- | --- | | **Search** | matches the title, the repository or the branch, case-insensitively | diff --git a/design/docs-images.json b/design/docs-images.json new file mode 100644 index 0000000..81ba351 --- /dev/null +++ b/design/docs-images.json @@ -0,0 +1,103 @@ +{ + "paperFileId": "01M25KM13W2VR3JT6KXR2Y7SHY", + "paperPageId": "W-1", + "paperPageName": "docs images", + "created": "2026-09-17", + "kind": "Design previews, not verified production screenshots", + "canvas": {"width": 1680, "height": 1120, "exportScale": 2}, + "window": {"width": 1440, "height": 944, "chromeHeight": 44, "radius": 12, "horizontalPadding": 120, "verticalPadding": 88, "trafficLightDiameter": 12, "trafficLightPitch": 20}, + "nativeWindowException": "Existing macOS windows retain their original 1440x900 frame and 52px chrome, centered with 110px vertical padding. No second chrome is drawn.", + "references": ["0657762d-1fc3-4f18-8f41-3fb4d85cba1b", "51221c95-4ba3-402e-a817-69c055fbbc1d"], + "wallpapers": [ + {"name": "alpine-day", "job": "f2e35854-293e-48cd-8b4b-976eb9f02c40"}, + {"name": "alpine-blue-hour", "job": "0d387430-f3c6-4739-abe4-ca3c324ee17a"}, + {"name": "floating-gardens-sunrise", "job": "a9c5d272-01ef-4993-bdb5-f0619e5ea595"}, + {"name": "floating-gardens-dusk", "job": "ac4db356-6c0b-4799-b4ab-c26822e79f75"}, + {"name": "tidal-coast-noon", "job": "39de5948-8602-4a42-976e-cde535b7fb3c"}, + {"name": "tidal-coast-twilight", "job": "8d61f6f9-2560-4ca0-8a71-dc99be1701cb"} + ], + "wallpaperGeneration": {"provider": "Higgsfield", "model": "gpt_image_2_5", "variant": "flare", "quality": "high", "resolution": "2k", "aspectRatio": "16:9", "width": 2688, "height": 1520}, + "notes": [ + "Compositions are authored and exported in Paper, not assembled by a rendering script.", + "Screens are unchanged 2x Paper exports, except the editable chat conversation clone and the redacted example key on settings-integrations-light.", + "Only actual source dark variants are used. A missing dark source is not replaced by an inverted or AI-recreated interface.", + "Gallery-only screens must not be automatically published in documentation.", + "The source file contains desktop application views, mobile variants, component specifications and error states. This gallery selects full desktop feature views; it is not an export of every artboard in the source file." + ], + "shots": [ + {"name": "chat-conversation-light", "node": "21WF-1", "source": "7LQ-0", "wallpaper": "alpine-day"}, + {"name": "chat-conversation-dark", "node": "21WL-1", "source": "1N6S-0", "wallpaper": "alpine-blue-hour"}, + {"name": "chat-home-light", "node": "222N-1", "source": "2IL-0", "wallpaper": "alpine-day"}, + {"name": "chat-home-dark", "node": "222T-1", "source": "9Y5-0", "wallpaper": "alpine-blue-hour"}, + {"name": "chat-model-picker-light", "node": "222Z-1", "source": "8LH-0", "wallpaper": "tidal-coast-noon"}, + {"name": "chat-attachments-light", "node": "2235-1", "source": "87Q-0", "wallpaper": "alpine-day"}, + {"name": "chat-voice-light", "node": "223B-1", "source": "8GV-0", "wallpaper": "tidal-coast-noon"}, + {"name": "chat-search-light", "node": "223H-1", "source": "8QE-0", "wallpaper": "alpine-day"}, + {"name": "chat-canvas-light", "node": "223N-1", "source": "9LY-0", "wallpaper": "floating-gardens-sunrise"}, + {"name": "chat-image-result-light", "node": "223T-1", "source": "9A7-0", "wallpaper": "floating-gardens-sunrise"}, + {"name": "projects-grid-light", "node": "224F-1", "source": "1VLF-0", "wallpaper": "floating-gardens-sunrise", "galleryOnly": "Mixed-language example project names"}, + {"name": "projects-grid-dark", "node": "224L-1", "source": "1VPM-0", "wallpaper": "floating-gardens-dusk", "galleryOnly": "Mixed-language example project names"}, + {"name": "project-workspace-light", "node": "224R-1", "source": "AK9-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "project-sources-light", "node": "224X-1", "source": "ALI-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "research-running-light", "node": "2253-1", "source": "ALB-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "research-report-light", "node": "2259-1", "source": "ALC-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "research-report-dark", "node": "225F-1", "source": "1W2U-0", "wallpaper": "floating-gardens-dusk"}, + {"name": "research-history-light", "node": "225L-1", "source": "ALE-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "library-files-light", "node": "225R-1", "source": "AKB-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "library-files-dark", "node": "225X-1", "source": "1VTE-0", "wallpaper": "floating-gardens-dusk"}, + {"name": "document-export-light", "node": "2263-1", "source": "JGV-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "library-detail-light", "node": "2269-1", "source": "ALU-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "connectors-catalog-light", "node": "226F-1", "source": "AKC-1", "wallpaper": "tidal-coast-noon"}, + {"name": "connector-permissions-light", "node": "226L-1", "source": "AKK-1", "wallpaper": "tidal-coast-noon"}, + {"name": "chat-memory-light", "node": "226R-1", "source": "AKJ-1", "wallpaper": "tidal-coast-noon"}, + {"name": "project-memory-light", "node": "226X-1", "source": "AKN-1", "wallpaper": "tidal-coast-noon"}, + {"name": "settings-billing-light", "node": "2287-1", "source": "AKG-1", "wallpaper": "alpine-day", "galleryOnly": "Unverified plan prices and quota values"}, + {"name": "settings-models-light", "node": "228D-1", "source": "CIO-1", "wallpaper": "alpine-day", "galleryOnly": "Per-ask automatic model selection conflicts with the documented fixed conversation model"}, + {"name": "settings-integrations-light", "node": "228J-1", "source": "AKR-1", "wallpaper": "alpine-day", "galleryOnly": "Key dialog is not the documented MCP setup screen; example credential redacted"}, + {"name": "settings-privacy-light", "node": "228P-1", "source": "AKT-1", "wallpaper": "alpine-day"}, + {"name": "settings-general-light", "node": "228V-1", "source": "EFJ-1", "wallpaper": "alpine-day"}, + {"name": "settings-code-light", "node": "2291-1", "source": "AKS-1", "wallpaper": "alpine-day"}, + {"name": "chat-editing-light", "node": "2297-1", "source": "AYT-1", "wallpaper": "alpine-day"}, + {"name": "chat-schedules-light", "node": "229D-1", "source": "AYX-1", "wallpaper": "alpine-day"}, + {"name": "chat-citations-light", "node": "229J-1", "source": "AZ2-1", "wallpaper": "alpine-day"}, + {"name": "chat-sharing-light", "node": "229P-1", "source": "AYV-1", "wallpaper": "alpine-day"}, + {"name": "chat-sidebar-light", "node": "229V-1", "source": "AKD-1", "wallpaper": "alpine-day"}, + {"name": "account-sign-in-light", "node": "22A1-1", "source": "ZLD-0", "wallpaper": "alpine-day"}, + {"name": "account-email-code-light", "node": "22A7-1", "source": "ZLX-0", "wallpaper": "alpine-day"}, + {"name": "work-empty-light", "node": "22AD-1", "source": "1TST-0", "wallpaper": "alpine-day", "galleryOnly": "The source Work page has only an empty state; no Work section exists in the documentation"}, + {"name": "code-session-light", "node": "22BB-1", "source": "1KID-0", "wallpaper": "tidal-coast-noon", "nativeChrome": true}, + {"name": "code-session-dark", "node": "22BH-1", "source": "1KYW-0", "wallpaper": "tidal-coast-twilight", "nativeChrome": true}, + {"name": "bot-workspace-light", "node": "22BN-1", "source": "15J9-0", "wallpaper": "floating-gardens-sunrise", "nativeChrome": true, "galleryOnly": "Mixed French and English conversation"}, + {"name": "bot-workspace-dark", "node": "22BT-1", "source": "1JO4-0", "wallpaper": "floating-gardens-dusk", "nativeChrome": true, "galleryOnly": "Mixed French and English conversation"}, + {"name": "bot-takeover-light", "node": "22BZ-1", "source": "1JG2-0", "wallpaper": "floating-gardens-sunrise", "nativeChrome": true, "galleryOnly": "French browser challenge content"}, + {"name": "code-sessions-light", "node": "22C5-1", "source": "AKE-1", "wallpaper": "tidal-coast-noon"}, + {"name": "code-automations-light", "node": "22CB-1", "source": "XS0-0", "wallpaper": "tidal-coast-noon", "galleryOnly": "Design run history exceeds the documented saved-recipe capability"}, + {"name": "security-overview-light", "node": "22CH-1", "source": "1P0Y-0", "wallpaper": "tidal-coast-noon", "galleryOnly": "Severity labels differ from the documented Blocking, Major, Minor and Note levels"}, + {"name": "bot-approvals-light", "node": "22CN-1", "source": "AKF-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "bot-routines-light", "node": "22CT-1", "source": "AOY-1", "wallpaper": "floating-gardens-sunrise", "galleryOnly": "Nothing runs on weekends overstates the configurable weekday default"}, + {"name": "bot-workspace-en-light", "node": "22E4-1", "source": "BK4-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "bot-attention-light", "node": "22EA-1", "source": "I10-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "bot-takeover-en-light", "node": "22EG-1", "source": "J9A-1", "wallpaper": "floating-gardens-sunrise"}, + {"name": "bot-settings-light", "node": "22EM-1", "source": "AP3-1", "wallpaper": "floating-gardens-sunrise", "galleryOnly": "Mini context length is shown as 32k, conflicting with the documented 262144-token context"}, + {"name": "code-home-light", "node": "22ES-1", "source": "14XX-0", "wallpaper": "tidal-coast-noon", "nativeChrome": true}, + {"name": "code-home-dark", "node": "22EY-1", "source": "1LR8-0", "wallpaper": "tidal-coast-twilight", "nativeChrome": true}, + {"name": "code-new-session-light", "node": "22F4-1", "source": "YIP-0", "wallpaper": "tidal-coast-noon"}, + {"name": "code-diff-review-light", "node": "22FA-1", "source": "AL0-1", "wallpaper": "tidal-coast-noon"}, + {"name": "code-checkpoints-light", "node": "22FG-1", "source": "AL2-1", "wallpaper": "tidal-coast-noon", "galleryOnly": "Code documentation explicitly states that checkpoints and rewind are unavailable"}, + {"name": "code-sharing-light", "node": "22FM-1", "source": "AL5-1", "wallpaper": "tidal-coast-noon", "galleryOnly": "Session sharing controls are not verified against the current documentation"}, + {"name": "code-environments-light", "node": "22FS-1", "source": "AL6-1", "wallpaper": "tidal-coast-noon", "galleryOnly": "Pro keeps 20 conflicts with the documented limit of 40 saved images per account"}, + {"name": "code-terminal-light", "node": "22FY-1", "source": "ALA-1", "wallpaper": "tidal-coast-noon"}, + {"name": "code-inline-comment-light", "node": "22GS-1", "source": "AL1-1", "wallpaper": "tidal-coast-noon"}, + {"name": "code-git-context-light", "node": "22GY-1", "source": "AL3-1", "wallpaper": "tidal-coast-noon", "galleryOnly": "The source context percentage does not match its token counts"}, + {"name": "code-usage-light", "node": "22H4-1", "source": "AL7-1", "wallpaper": "tidal-coast-noon", "galleryOnly": "Unverified prices and quotas; Pro and Free plan labels disagree"}, + {"name": "security-review-console-light", "node": "22HA-1", "source": "AL8-1", "wallpaper": "tidal-coast-noon", "galleryOnly": "Severity labels differ from documented review levels"}, + {"name": "code-continue-light", "node": "22HG-1", "source": "AL9-1", "wallpaper": "tidal-coast-noon", "galleryOnly": "Command-level resumption claim is not established by the documented Continue follow-up"}, + {"name": "code-vm-preview-light", "node": "22HM-1", "source": "1QZ7-0", "wallpaper": "tidal-coast-noon", "nativeChrome": true, "galleryOnly": "The documentation marks the Code Desktop tab as unavailable"}, + {"name": "security-findings-light", "node": "22HS-1", "source": "1RNT-0", "wallpaper": "tidal-coast-noon", "galleryOnly": "Severity labels differ from documented review levels"}, + {"name": "security-findings-dark", "node": "22HY-1", "source": "1PEJ-0", "wallpaper": "tidal-coast-twilight", "galleryOnly": "Severity labels differ from documented review levels; not an exact counterpart of the light source"}, + {"name": "security-finding-detail-light", "node": "22I4-1", "source": "1S1F-0", "wallpaper": "tidal-coast-noon", "galleryOnly": "Severity labels differ from documented review levels"}, + {"name": "security-onboarding-light", "node": "22IA-1", "source": "YVW-0", "wallpaper": "tidal-coast-noon", "galleryOnly": "Every pull request and every push overstate the documented draft exclusions and configurable review triggers"}, + {"name": "security-scope-light", "node": "22IG-1", "source": "1SC7-0", "wallpaper": "tidal-coast-noon", "galleryOnly": "The depicted risk-category onboarding controls are not established by the documented setup"}, + {"name": "bot-channels-light", "node": "22IM-1", "source": "AP0-1", "wallpaper": "floating-gardens-sunrise", "galleryOnly": "Multi-bot rooms are documented as not operational"} + ] +} diff --git a/design/docs-images.md b/design/docs-images.md new file mode 100644 index 0000000..cee743e --- /dev/null +++ b/design/docs-images.md @@ -0,0 +1,49 @@ +# Documentation images + +The source gallery is the **docs images** page in **Cortex DA 2**: +https://app.paper.design/file/01M25KM13W2VR3JT6KXR2Y7SHY/W-1 + +## Source and format + +- 74 desktop application compositions, two reusable macOS templates and six + full-background Higgsfield wallpapers. +- Composition canvas: 1680 x 1120. WebP exports: 3360 x 2240, at 2x. +- Application content comes from existing Paper designs, not generated UI. +- Existing macOS windows keep their native chrome. Other screens use the + shared 44px title bar. Every image shows a complete application viewport. +- Light and dark pairings use actual matching source states. No missing dark + variant is synthesized by recoloring a light screenshot. + +These are **interface design previews**, not verified production captures. +Documentation frames say "Interface preview." Product behavior and limitations +in the article remain authoritative. + +## Publication boundary + +`docs-images.json` maps each composition to its source node, wallpaper and +Higgsfield generation. `galleryOnly` explains why an image must not be placed +in an article or copied into the public `images/product/` directory. + +Excluded previews remain in Paper: unimplemented functionality, conflicting +limits or labels, unverified prices, mixed-language examples and the redacted +integration-key dialog. The source artboards are unchanged. + +The selection covers full desktop feature views, including settings and +natural dialogs. It deliberately excludes mobile duplicates, component +specimens and redundant error states. No dedicated Cortex Design workspace, +CLI screen, SSH-host management screen or running Security scan was found in +the supplied sources. A Chat document canvas is not a Cortex Design screen. + +## Updating an image + +1. Update the composition in Paper, preserving the original source artboard. +2. Review framing, loaded image fills, chrome, language and visible claims. +3. Export the named composition as WebP at 2x. +4. Copy the approved export to `images/product/.webp`. +5. Retain a meaningful alt description and explicit dimensions in MDX. +6. Put theme visibility classes on an enclosing `div`, not on the image, + so the hidden variant's generated zoom control is hidden as well. +7. Run `node scripts/tests/docs-images.test.mjs` and the repository checks. + +Do not automatically publish the entire gallery. An image excluded for a +content mismatch needs a fresh product check before its exclusion is removed. diff --git a/getting-started/accounts.mdx b/getting-started/accounts.mdx index bbb42a4..e8b5e8a 100644 --- a/getting-started/accounts.mdx +++ b/getting-started/accounts.mdx @@ -38,6 +38,10 @@ Guest conversations live in the browser session. Sign in before you close the ta Sign-in happens in a dialog on `cortex.foundation`, and the dialog says so: `Sign-in happens here, on cortex.foundation — never on another domain.` It is titled **Sign in**, and it explains what an account buys you with `Your chats, projects and Bot routines follow you across devices. Nothing is lost when you close the tab.` + + The Cortex sign-in dialog over Chat with Google, Apple, email, and single sign-on options. + + Choose **Sign in to save your work** in the sidebar footer, or **Sign in** on any public page. diff --git a/getting-started/data-and-privacy.mdx b/getting-started/data-and-privacy.mdx index 84410a2..4b90f4c 100644 --- a/getting-started/data-and-privacy.mdx +++ b/getting-started/data-and-privacy.mdx @@ -12,6 +12,10 @@ This page walks through each section of that tab in the order you meet it, says Most of this tab needs an account. A guest can open it and read it, but export, deletion, memory, activity and two-factor all say so and offer to sign in rather than disappearing. + + The Data and privacy tab in Cortex Settings with account data and privacy controls. + + ## Export your data **Export my data** builds a JSON file, `cortex-data-export.json`, while you wait. There is no queue and no status page. diff --git a/getting-started/keyboard-shortcuts.mdx b/getting-started/keyboard-shortcuts.mdx index e9eedb1..b567736 100644 --- a/getting-started/keyboard-shortcuts.mdx +++ b/getting-started/keyboard-shortcuts.mdx @@ -42,6 +42,10 @@ palette in Code. The short list inside **Settings** labels `⌘ K` as **New chat actually opens is Search in Chat and the palette in Code, so treat it as a jump-anywhere key rather than a create key. + + The Chat search palette opened with the keyboard shortcut, with a search field and matching results. + + Neither list in the product is complete. The shortcut sheet carries four rows and leaves out `⌘ \`, which **Settings** advertises and the app binds. The table above is the union of both, so it diff --git a/getting-started/quickstart.mdx b/getting-started/quickstart.mdx index 8df77c6..39b8659 100644 --- a/getting-started/quickstart.mdx +++ b/getting-started/quickstart.mdx @@ -8,6 +8,10 @@ This page takes you from a browser tab to a useful first session. The first answ It covers what each task requires, the five steps to a first answer, how signing in changes the screen, and where to go next for the application you actually came for. + + The Chat sidebar with workspace navigation, pinned conversations, recent chats, and an open conversation action menu. + + ## What you need | To do this | You need | diff --git a/getting-started/settings.mdx b/getting-started/settings.mdx index 3df69a2..bbb7b95 100644 --- a/getting-started/settings.mdx +++ b/getting-started/settings.mdx @@ -8,6 +8,10 @@ Settings in Cortex is an overlay dialog, not a page. It opens over whatever you This page names every tab and the controls inside it, so you can find a setting you cannot place. Depth on a specific area lives on that area's page, and this page links out where that is true. + + The General tab in the Cortex Settings overlay with account details and interface preferences. + + ## Open Settings Choose **Settings** in the sidebar footer. Two deep links open the overlay directly: `cortex.foundation/settings` opens it over Chat, and `cortex.foundation/code/settings` opens it over Code. Adding `?tab=` and a tab slug opens a named tab, and an unknown value falls back to **General**. The slugs are `general`, `models`, `integrations`, `code`, `approvals`, `data-privacy` and `plan-billing`. diff --git a/images/product/account-sign-in-light.webp b/images/product/account-sign-in-light.webp new file mode 100644 index 0000000..4b824de Binary files /dev/null and b/images/product/account-sign-in-light.webp differ diff --git a/images/product/bot-approvals-light.webp b/images/product/bot-approvals-light.webp new file mode 100644 index 0000000..23009fa Binary files /dev/null and b/images/product/bot-approvals-light.webp differ diff --git a/images/product/bot-attention-light.webp b/images/product/bot-attention-light.webp new file mode 100644 index 0000000..6615ba6 Binary files /dev/null and b/images/product/bot-attention-light.webp differ diff --git a/images/product/bot-takeover-en-light.webp b/images/product/bot-takeover-en-light.webp new file mode 100644 index 0000000..4a75901 Binary files /dev/null and b/images/product/bot-takeover-en-light.webp differ diff --git a/images/product/bot-workspace-en-light.webp b/images/product/bot-workspace-en-light.webp new file mode 100644 index 0000000..e3abb19 Binary files /dev/null and b/images/product/bot-workspace-en-light.webp differ diff --git a/images/product/chat-attachments-light.webp b/images/product/chat-attachments-light.webp new file mode 100644 index 0000000..a5f8f73 Binary files /dev/null and b/images/product/chat-attachments-light.webp differ diff --git a/images/product/chat-canvas-light.webp b/images/product/chat-canvas-light.webp new file mode 100644 index 0000000..d541f77 Binary files /dev/null and b/images/product/chat-canvas-light.webp differ diff --git a/images/product/chat-citations-light.webp b/images/product/chat-citations-light.webp new file mode 100644 index 0000000..aedf7f2 Binary files /dev/null and b/images/product/chat-citations-light.webp differ diff --git a/images/product/chat-conversation-dark.webp b/images/product/chat-conversation-dark.webp new file mode 100644 index 0000000..f13531d Binary files /dev/null and b/images/product/chat-conversation-dark.webp differ diff --git a/images/product/chat-conversation-light.webp b/images/product/chat-conversation-light.webp new file mode 100644 index 0000000..35d1af0 Binary files /dev/null and b/images/product/chat-conversation-light.webp differ diff --git a/images/product/chat-editing-light.webp b/images/product/chat-editing-light.webp new file mode 100644 index 0000000..147fce5 Binary files /dev/null and b/images/product/chat-editing-light.webp differ diff --git a/images/product/chat-home-dark.webp b/images/product/chat-home-dark.webp new file mode 100644 index 0000000..2086547 Binary files /dev/null and b/images/product/chat-home-dark.webp differ diff --git a/images/product/chat-home-light.webp b/images/product/chat-home-light.webp new file mode 100644 index 0000000..21057fc Binary files /dev/null and b/images/product/chat-home-light.webp differ diff --git a/images/product/chat-image-result-light.webp b/images/product/chat-image-result-light.webp new file mode 100644 index 0000000..c912038 Binary files /dev/null and b/images/product/chat-image-result-light.webp differ diff --git a/images/product/chat-memory-light.webp b/images/product/chat-memory-light.webp new file mode 100644 index 0000000..2fa1aa2 Binary files /dev/null and b/images/product/chat-memory-light.webp differ diff --git a/images/product/chat-model-picker-light.webp b/images/product/chat-model-picker-light.webp new file mode 100644 index 0000000..439fbb1 Binary files /dev/null and b/images/product/chat-model-picker-light.webp differ diff --git a/images/product/chat-schedules-light.webp b/images/product/chat-schedules-light.webp new file mode 100644 index 0000000..0fc876a Binary files /dev/null and b/images/product/chat-schedules-light.webp differ diff --git a/images/product/chat-search-light.webp b/images/product/chat-search-light.webp new file mode 100644 index 0000000..a57d0c2 Binary files /dev/null and b/images/product/chat-search-light.webp differ diff --git a/images/product/chat-sidebar-light.webp b/images/product/chat-sidebar-light.webp new file mode 100644 index 0000000..a3267c5 Binary files /dev/null and b/images/product/chat-sidebar-light.webp differ diff --git a/images/product/chat-voice-light.webp b/images/product/chat-voice-light.webp new file mode 100644 index 0000000..438a111 Binary files /dev/null and b/images/product/chat-voice-light.webp differ diff --git a/images/product/code-diff-review-light.webp b/images/product/code-diff-review-light.webp new file mode 100644 index 0000000..478b013 Binary files /dev/null and b/images/product/code-diff-review-light.webp differ diff --git a/images/product/code-home-dark.webp b/images/product/code-home-dark.webp new file mode 100644 index 0000000..575867a Binary files /dev/null and b/images/product/code-home-dark.webp differ diff --git a/images/product/code-home-light.webp b/images/product/code-home-light.webp new file mode 100644 index 0000000..4e26082 Binary files /dev/null and b/images/product/code-home-light.webp differ diff --git a/images/product/code-new-session-light.webp b/images/product/code-new-session-light.webp new file mode 100644 index 0000000..620c712 Binary files /dev/null and b/images/product/code-new-session-light.webp differ diff --git a/images/product/code-session-dark.webp b/images/product/code-session-dark.webp new file mode 100644 index 0000000..300f82f Binary files /dev/null and b/images/product/code-session-dark.webp differ diff --git a/images/product/code-session-light.webp b/images/product/code-session-light.webp new file mode 100644 index 0000000..bcc72e2 Binary files /dev/null and b/images/product/code-session-light.webp differ diff --git a/images/product/code-sessions-light.webp b/images/product/code-sessions-light.webp new file mode 100644 index 0000000..f8e5c9b Binary files /dev/null and b/images/product/code-sessions-light.webp differ diff --git a/images/product/code-terminal-light.webp b/images/product/code-terminal-light.webp new file mode 100644 index 0000000..553b7cc Binary files /dev/null and b/images/product/code-terminal-light.webp differ diff --git a/images/product/connectors-catalog-light.webp b/images/product/connectors-catalog-light.webp new file mode 100644 index 0000000..ca06141 Binary files /dev/null and b/images/product/connectors-catalog-light.webp differ diff --git a/images/product/document-export-light.webp b/images/product/document-export-light.webp new file mode 100644 index 0000000..137ed04 Binary files /dev/null and b/images/product/document-export-light.webp differ diff --git a/images/product/library-files-dark.webp b/images/product/library-files-dark.webp new file mode 100644 index 0000000..0070f93 Binary files /dev/null and b/images/product/library-files-dark.webp differ diff --git a/images/product/library-files-light.webp b/images/product/library-files-light.webp new file mode 100644 index 0000000..35c7475 Binary files /dev/null and b/images/product/library-files-light.webp differ diff --git a/images/product/project-sources-light.webp b/images/product/project-sources-light.webp new file mode 100644 index 0000000..3fb174a Binary files /dev/null and b/images/product/project-sources-light.webp differ diff --git a/images/product/project-workspace-light.webp b/images/product/project-workspace-light.webp new file mode 100644 index 0000000..ae29506 Binary files /dev/null and b/images/product/project-workspace-light.webp differ diff --git a/images/product/research-report-dark.webp b/images/product/research-report-dark.webp new file mode 100644 index 0000000..155212d Binary files /dev/null and b/images/product/research-report-dark.webp differ diff --git a/images/product/research-report-light.webp b/images/product/research-report-light.webp new file mode 100644 index 0000000..a3f63d9 Binary files /dev/null and b/images/product/research-report-light.webp differ diff --git a/images/product/research-running-light.webp b/images/product/research-running-light.webp new file mode 100644 index 0000000..047b99d Binary files /dev/null and b/images/product/research-running-light.webp differ diff --git a/images/product/settings-general-light.webp b/images/product/settings-general-light.webp new file mode 100644 index 0000000..c31006a Binary files /dev/null and b/images/product/settings-general-light.webp differ diff --git a/images/product/settings-privacy-light.webp b/images/product/settings-privacy-light.webp new file mode 100644 index 0000000..c7b6a4c Binary files /dev/null and b/images/product/settings-privacy-light.webp differ diff --git a/scripts/check-docs-content.mjs b/scripts/check-docs-content.mjs index af6f773..0b550c8 100644 --- a/scripts/check-docs-content.mjs +++ b/scripts/check-docs-content.mjs @@ -3,13 +3,14 @@ * Content checks for the documentation tree. * * `check-docs-site.mjs` checks the site's shape: that `docs.json` and the MDX tree agree - * both ways, that the problem pages match the backend's error enum, that no image or - * sign-in internal has crept in, and that every page has frontmatter. This script checks + * both ways, that the problem pages match the backend's error enum, that screenshots + * are safe local files, and that no sign-in internal has crept in. This script checks * what is inside a page: * * - frontmatter completeness, the 160-character description ceiling Mintlify renders * into meta tags, and titles that are unique across the site - * - the forbidden-vocabulary list: sign-in wire internals, image paths, third-party + * - accessible local product screenshots, with no remote media or unsafe paths + * - the forbidden-vocabulary list: sign-in wire internals, third-party * vendor and competitor names, and emoji * - `/v1/` API paths, which belong only on the pages that document the API * - internal links resolving to a real page, and external links staying inside the @@ -28,6 +29,7 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'; import { join, relative, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { checkProductImages } from './docs-images.mjs'; const ROOT = resolve(process.env.CORTEX_CHECK_ROOT ?? fileURLToPath(new URL('..', import.meta.url))); @@ -41,12 +43,6 @@ const FORBIDDEN = [ [/\bWorkOS\b/, 'WorkOS'], [/client_secret/i, 'client_secret'], [/docs\.cortex\.sh/, 'docs.cortex.sh'], - // No images anywhere in this site, by design. - [/\/images\//, 'image path'], - [/'], - [/'], - [/!\[[^\]]*\]\(/, 'markdown image'], - [/^image:/m, 'image frontmatter'], // Competitors and third-party vendors. Cortex documentation describes Cortex. [/\bChatGPT\b/, 'competitor: ChatGPT'], [/\bOpenAI\b/, 'competitor: OpenAI'], @@ -94,7 +90,7 @@ const FORBIDDEN = [ const COMPONENTS = [ 'Steps', 'Step', 'Tabs', 'Tab', 'AccordionGroup', 'Accordion', 'CardGroup', 'Card', - 'Note', 'Tip', 'Warning', 'Info', 'Check', 'Update', + 'Note', 'Tip', 'Warning', 'Info', 'Check', 'Update', 'Frame', ]; const EXTERNAL_ALLOWED = [ @@ -193,6 +189,7 @@ function checkPage(slug, ctx) { out.push(`frontmatter: description is ${fm.description.length} chars (max 160)`); } if (!isProblem && !fm.icon) out.push('frontmatter: missing icon'); + out.push(...checkProductImages(text, ROOT)); for (const [re, label] of FORBIDDEN) { const m = re.exec(text); @@ -241,7 +238,7 @@ function checkPage(slug, ctx) { if (opens !== closes) out.push(`unbalanced <${tag}>: ${opens} open, ${closes} close`); } - // Every card carries an icon, because this site has no images to carry instead. + // Screenshots in articles do not replace navigation card icons. for (const m of body.matchAll(/]*>/g)) { if (!/icon=/.test(m[0])) out.push(` without icon: ${m[0].slice(0, 80)}`); } diff --git a/scripts/check-docs-site.mjs b/scripts/check-docs-site.mjs index 670685c..e2c6382 100644 --- a/scripts/check-docs-site.mjs +++ b/scripts/check-docs-site.mjs @@ -13,8 +13,8 @@ * * The site is end-user documentation. Public MDX must not document sign-in * wire protocols (`/auth/`, `/oauth/`, refresh tokens, identity vendors, or the - * session cookie), and it carries no images: every page and card uses an - * icon. Navigation is `navigation.tabs`, one tab per application, so a reader + * session cookie). Product screenshots are accessible local files; pages and + * cards retain their icons. Navigation is `navigation.tabs`, one tab per application, so a reader * picks the product from the navbar the way they would on any large docs site. * Every navigation entry, group root, navbar link, and internal href must * resolve to an MDX page — Mintlify would otherwise publish a link that 404s @@ -24,6 +24,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'; import { join, relative, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; +import { checkProductImages } from './docs-images.mjs'; const ROOT = resolve(process.env.CORTEX_CHECK_ROOT ?? fileURLToPath(new URL('..', import.meta.url))); const BACKEND = process.argv[2] === undefined ? null : resolve(process.argv[2]); @@ -216,21 +217,9 @@ function assertNoAuthInternals(rel, text) { } } -const IMAGES = [ - { pattern: /' }, - { pattern: /' }, - { pattern: /!\[[^\]]*\]\(/, label: 'a markdown image' }, - { pattern: /^image:/m, label: '`image:` frontmatter' }, - { pattern: /\/images\//, label: 'an /images/ path' }, -]; - -function assertNoImages(rel, text) { +function assertImages(rel, text) { if (!rel.endsWith('.mdx')) return; - for (const { pattern, label } of IMAGES) { - if (pattern.test(text)) { - fail(`${rel} uses ${label}; this site carries no images, use icons`); - } - } + for (const error of checkProductImages(text, ROOT)) fail(`${rel}: ${error}`); } function frontmatter(text) { @@ -254,7 +243,7 @@ function assertFrontmatter(rel, text, titles) { if (!fm.title) fail(`${rel} has no title`); if (!fm.description) fail(`${rel} has no description`); else if (fm.description.length > 160) fail(`${rel} description is ${fm.description.length} characters (max 160)`); - if (!rel.startsWith('problems/') && !fm.icon) fail(`${rel} has no icon (this site uses icons, not images)`); + if (!rel.startsWith('problems/') && !fm.icon) fail(`${rel} has no icon`); if (fm.title) { const other = titles.get(fm.title); if (other !== undefined) fail(`${rel} repeats the title "${fm.title}" of ${other}`); @@ -396,7 +385,7 @@ for (const file of docsFiles) { fail(`${rel} names docs.cortex.sh (problem URIs belong on docs.cortex.foundation)`); } assertNoAuthInternals(rel, text); - assertNoImages(rel, text); + assertImages(rel, text); assertFrontmatter(rel, text, titles); if (rel.endsWith('.mdx')) { for (const slug of internalHrefs(text)) { diff --git a/scripts/docs-images.mjs b/scripts/docs-images.mjs new file mode 100644 index 0000000..363e58a --- /dev/null +++ b/scripts/docs-images.mjs @@ -0,0 +1,140 @@ +import { realpathSync, statSync } from 'node:fs'; +import { isAbsolute, join, relative, resolve, sep } from 'node:path'; + +const IMAGE_ATTRIBUTES = new Set([ + 'src', 'alt', 'width', 'height', 'className', 'loading', 'decoding', 'title', +]); +const MEDIA_TAGS = new Set([ + 'image', 'picture', 'source', 'video', 'audio', 'iframe', 'embed', 'object', 'svg', +]); + +function inside(directory, file) { + const path = relative(directory, file); + return path !== '' && path !== '..' && !path.startsWith(`..${sep}`) && !isAbsolute(path); +} + +function imagePathError(root, src) { + if (!src?.startsWith('/images/product/') || + !/^\/images\/product\/(?:[a-z0-9][a-z0-9_-]*\/)*[a-z0-9][a-z0-9._-]*\.(?:webp|png|jpe?g)$/i.test(src)) { + return 'image src must be a safe local /images/product/ path ending in WebP, PNG or JPG (no remote URLs, escapes, queries or fragments)'; + } + const file = resolve(root, src.slice(1)); + try { + // Resolve the target too: a symlink must not turn a local URL into an escape. + const directory = join(realpathSync(root), 'images', 'product'); + if (!inside(directory, realpathSync(file))) { + return `unsafe image path outside images/product/: ${src}`; + } + const stat = statSync(file); + if (!stat.isFile() || stat.size === 0) return `image path must name a nonempty file: ${src}`; + } catch { + return `missing or unreadable local image: ${src}`; + } + return null; +} + +function hasAltText(value) { + const decoded = (value ?? '') + .replace(/&#(x[0-9a-f]+|\d+);/gi, (_, code) => { + const number = code[0].toLowerCase() === 'x' ? parseInt(code.slice(1), 16) : Number(code); + return number > 0 && number <= 0x10ffff ? String.fromCodePoint(number) : ''; + }) + .replace(/&(?:nbsp|ensp|emsp|thinsp|hairsp|Tab|NewLine|ZeroWidthSpace);/gi, ' '); + return decoded.replace(/[\s\p{C}]/gu, '').length > 0; +} + +function mask(text) { + return text.replace(/[^\n]/g, ' '); +} + +function imageMarkup(text) { + // Code samples and comments do not load media. Preserve offsets for diagnostics. + return text + .replace(/^ {0,3}(`{3,}|~{3,})[^\n]*\n[\s\S]*?^ {0,3}\1[ \t]*(?=\n|$)/gm, mask) + .replace(/(`+)[^\n]*?\1/g, mask) + .replace(/|{\/\*[\s\S]*?\*\/}/g, mask); +} + +/** Check the intentionally narrow, literal syntax used by product docs. */ +export function checkProductImages(text, root) { + const errors = []; + const normalized = text.replace(/\r/g, ''); + const frontmatter = /^---\n([\s\S]*?)\n---(?:\n|$)/.exec(normalized); + if (frontmatter && /^\s*(?:image|"image"|'image')\s*:/mi.test(frontmatter[1])) { + errors.push('image frontmatter is unsupported; use an accessible local in the article'); + } + const body = imageMarkup(frontmatter ? mask(frontmatter[0]) + normalized.slice(frontmatter[0].length) : normalized); + if (/(? with alt text'); + } + + // Scan tags with quoted values intact, including multiline attributes and ">" in alt text. + const tags = /<(\/?)([A-Za-z][\w.:-]*)(?=[\s/>])/g; + for (let match; (match = tags.exec(body));) { + const [, closing, name] = match; + let end = tags.lastIndex; + let quote = null; + for (; end < body.length; end++) { + const char = body[end]; + if (quote) { + if (char === quote) quote = null; + } else if (char === '"' || char === "'") { + quote = char; + } else if (char === '>' || char === '<') { + break; + } + } + const lower = name.toLowerCase(); + const label = `image at line ${body.slice(0, match.index).split('\n').length}`; + const fail = (message) => errors.push(`${label}: ${message}`); + const attributes = body.slice(tags.lastIndex, end); + tags.lastIndex = end; + if (lower !== 'img') { + if (!closing && (MEDIA_TAGS.has(lower) || + /\b(?:src|srcset|poster|image|img)\s*=/i.test(attributes))) { + fail(`unsupported media <${name}>; use a local , optionally wrapped in `); + } + continue; + } + if (name !== 'img' || closing || body[end] !== '>' || !/\/\s*$/.test(attributes)) { + fail('use a self-closing lowercase with literal quoted attributes'); + continue; + } + const source = attributes.replace(/\/\s*$/, ''); + const attribute = /\s+([A-Za-z][\w-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/y; + const values = new Map(); + let position = 0; + while (position < source.length) { + if (!source.slice(position).trim()) break; + attribute.lastIndex = position; + const item = attribute.exec(source); + if (!item) { + fail('image attributes must be literal quoted values; no expressions or spreads'); + break; + } + const [, key, double, single] = item; + if (!IMAGE_ATTRIBUTES.has(key)) fail(`unsupported image attribute ${key}`); + if (values.has(key)) fail(`duplicate image attribute ${key}`); + values.set(key, double ?? single); + position = attribute.lastIndex; + } + if (!hasAltText(values.get('alt'))) fail('image alt text must be nonempty'); + const pathError = imagePathError(root, values.get('src')); + if (pathError) fail(pathError); + for (const dimension of ['width', 'height']) { + if (values.has(dimension) && !/^[1-9]\d*$/.test(values.get(dimension))) { + fail(`image ${dimension} must be a positive integer`); + } + } + if (values.has('className') && !/^[\w:\s-]+$/.test(values.get('className'))) { + fail('image className must contain plain class names, not styles or media URLs'); + } + if (values.has('loading') && !['lazy', 'eager'].includes(values.get('loading'))) { + fail('image loading must be lazy or eager'); + } + if (values.has('decoding') && !['async', 'sync', 'auto'].includes(values.get('decoding'))) { + fail('image decoding must be async, sync or auto'); + } + } + return errors; +} diff --git a/scripts/tests/check-docs-content.test.sh b/scripts/tests/check-docs-content.test.sh index a284c53..a996850 100644 --- a/scripts/tests/check-docs-content.test.sh +++ b/scripts/tests/check-docs-content.test.sh @@ -4,7 +4,7 @@ # script claims to enforce actually fires: frontmatter, the description ceiling, # duplicate titles, forbidden vocabulary, emoji, API paths off the API pages, dead # links, unallowed external links, the closing section, page length, unbalanced -# components, iconless cards and a hub with no card grid. +# components, iconless cards, unsafe screenshots and a hub with no card grid. # # It also pins the two deliberate exemptions, which are the ones a future edit is most # likely to break by accident: the card-suit block is not emoji, and problem pages are @@ -194,10 +194,6 @@ d="$(fresh auth-internal)" printf '\nThe `refresh_token` is rotated.\n' >> "$d/site/guide/thing.mdx" must_fail "$d" "refresh_token" -d="$(fresh image)" -printf '\n![A picture](/images/thing.png)\n' >> "$d/site/guide/thing.mdx" -must_fail "$d" "markdown image" - d="$(fresh emoji)" printf '\nShip it '"$(printf '\xf0\x9f\x9a\x80')"'\n' >> "$d/site/guide/thing.mdx" must_fail "$d" "emoji" @@ -209,6 +205,64 @@ printf '\nThe status line reads `'"$(printf '\xe2\x99\xa6')"' Thought for 4s`.\n >> "$d/site/guide/thing.mdx" out="$(run "$d")" || fail "a card suit is not emoji, got: $out" +# --- local product screenshots --------------------------------------------------- +d="$(fresh images)" +mkdir -p "$d/site/images/product" +for file in shot-light.webp shot-dark.webp shot.png shot.jpg; do + printf 'screenshot fixture' > "$d/site/images/product/$file" +done +cat >> "$d/site/guide/thing.mdx" <<'MDX' + + + Cortex home + Cortex home in dark mode + +Changes > proposed files +The library + +`` +```mdx + +``` +MDX +out="$(run "$d")" || fail "accessible local screenshots should pass, got: $out" + +image_failure() { + local dir markup="$2" needle="$3" + dir="$(fresh "image-$1")" + mkdir -p "$dir/site/images/product" + printf 'screenshot fixture' > "$dir/site/images/product/shot.webp" + printf '\n%s\n' "$markup" >> "$dir/site/guide/thing.mdx" + must_fail "$dir" "$needle" +} + +image_failure no-alt '' 'alt text must be nonempty' +image_failure empty-alt '  ' 'alt text must be nonempty' +image_failure missing 'Cortex home' 'missing or unreadable' +image_failure remote 'Cortex home' 'safe local' +image_failure protocol-relative 'Cortex home' 'safe local' +image_failure data 'Cortex home' 'safe local' +image_failure traversal 'Cortex home' 'safe local' +image_failure encoded 'Cortex home' 'safe local' +image_failure svg 'Cortex home' 'safe local' +image_failure srcset 'Cortex home' 'unsupported image attribute' +image_failure expression '{description}' 'literal quoted' +image_failure spread 'Cortex home' 'literal quoted' +image_failure media '