diff --git a/packages/skills/skills/penguin-sdk/SKILL.md b/packages/skills/skills/penguin-sdk/SKILL.md index 6fbf1d1..c943057 100644 --- a/packages/skills/skills/penguin-sdk/SKILL.md +++ b/packages/skills/skills/penguin-sdk/SKILL.md @@ -1,10 +1,10 @@ --- name: penguin-sdk -description: Build AI apps on the Penguin Harness SDK — self-contained projects, the createSession/run streaming loop, and a complete RAG recipe that ingests documents into a knowledge base and answers with citations behind a web UI. +description: Build AI apps on the Penguin Harness SDK — self-contained projects, the createSession/run streaming loop with thinking and image messages, and a complete RAG recipe that ingests documents into a knowledge base and answers with citations behind a web UI. short_description: Build AI and RAG apps on the Penguin Harness SDK. short_description_zh: 基于 Penguin SDK 构建 AI 与 RAG 应用。 -version: 14 -updated: 2026-07-25T00:00:00Z +version: 17 +updated: 2026-07-30T11:10:00Z --- # Penguin Harness SDK @@ -31,23 +31,19 @@ const agent = await createAgent({ root: path.join(import.meta.dirname, "penguin_ With every reference relative to the project, the user can move or copy the folder anywhere and it still runs. -## Before you build: keys and the data root +## Keys and the data root — check before you build -**Important prerequisite — set the key up first, then develop.** For AI-app development, have the user add the model API key in **this agent's key vault** (gear icon on its card, Agents page → settings → key vault tab) *before* you start building, so the credential is in your shell environment when you configure and test the app. Model ids to offer the user can come straight from the penguin CLI catalog (`penguin config model add --help`, and the agenthub-models skill's id table). +**The app's Penguin data root must live inside the CWD workspace — never `~/.penguin`.** Point `createAgent({ root })` and every `penguin config ... --root ` at a directory under the current working directory (e.g. `./penguin_data`); the global `~/.penguin` belongs to the person running Penguin and must never hold — or lend — the app's config or keys. -**The app's Penguin data root must live inside the CWD workspace — never `~/.penguin`.** Point `createAgent({ root })` and every `penguin config ... --root ` at a directory under the current working directory (e.g. `./penguin_data`); the global `~/.penguin` directory belongs to the person running Penguin and must never hold the app's config or keys. - -## Check the model first - -Before writing any code, verify a usable model credential exists — a finished app that cannot answer is a failed delivery discovered too late: +**Credential first, code second** — a finished app that cannot answer is a failed delivery discovered too late. Before writing any code: ```bash env | grep -oE "(DEEPSEEK|OPENAI|ANTHROPIC|GEMINI)_API_KEY" || echo none ``` -Vault keys also appear in your Vault Keys section. **Only two sources count as a usable credential**: a vault-injected environment variable (the check above), or a key already configured in the app's own data root (`penguin config model list --root `). Keys living in the global `~/.penguin` or any other `.penguin` directory do **not** count — a bare `penguin config model list` (no `--root`) reads the global store, because the CLI defaults to the global root unless `--root` is given, so a key showing up there proves nothing for the app and must never be used or copied. +**Only two sources count as a usable credential**: a vault-injected environment variable (the check above; vault keys also appear in your Vault Keys section), or a key already configured in the app's own data root (`penguin config model list --root `). Keys in the global `~/.penguin` or any other `.penguin` directory do **not** count — a bare `penguin config model list` (no `--root`) reads the global store, because the CLI defaults to the global root unless `--root` is given, so a key showing up there proves nothing for the app and must never be used or copied. -If neither counted source yields a usable key, **stop immediately and ask the user to configure one — do not start building, and do not keep calling tools to retry**: ask them to open the agent's settings via the **gear icon** on its card (left side, Agents page) and add a model API key (e.g. `DEEPSEEK_API_KEY`) in the **key vault** tab — vault values reach your shell environment on the next task. Re-running `env`, re-checking the vault, or attempting the build in a loop wastes turns and money; one clear check, then hand back to the user. Build only after a credential is confirmed, or clearly agree with the user to build now and verify later. +If neither counted source yields a key, **stop immediately and ask the user to configure one — do not start building, and do not burn turns re-checking in a loop**: have them open this agent's settings via the **gear icon** on its card (left side, Agents page) and add a model API key (e.g. `DEEPSEEK_API_KEY`) in the **key vault** tab — vault values reach your shell environment on the next task. One clear check, then hand back to the user. Build only after a credential is confirmed, or after clearly agreeing with the user to build now and verify later. Model ids to offer the user come from the penguin CLI catalog (`penguin config model add --help`) and the agenthub-models skill's id table. ## Setup @@ -66,7 +62,7 @@ Keep model API keys **project-local**: configure them with the penguin CLI into Model config lives in one hidden file under the data root's project directory: `.project_config.toml`. It is CLI-only — never read, print or edit it. -If neither route yields a usable credential, do not fake the verification: finish the build, report it as **unverified**, and tell the user exactly how to unblock you — in the Penguin web app, open this agent's settings via the **gear icon** on its card (Agents page) and add a model API key (e.g. `DEEPSEEK_API_KEY`) under the **key vault** tab. Vault keys are injected into your shell environment on the next task, so once the user has added one, you can run the self-test to completion. +If the user agreed to build before a credential exists, do not fake the verification: finish the build, report it as **unverified**, and point them at the key vault flow above — once a key is added, vault values reach your environment on the next task and you can run the self-test to completion. ## Streaming loop @@ -91,6 +87,8 @@ for (;;) { if (isModelMessage(msg)) { const p = msg.payload; if (p.type === "partial_text" && p.event_type === "delta") process.stdout.write(p.text); + // CoT stream from reasoning models — show progress, but keep it out of the answer channel. + if (p.type === "partial_thinking" && p.event_type === "delta") process.stderr.write(p.thinking); } } process.stdout.write("\n"); @@ -101,8 +99,26 @@ session.dispose(); - `createSession({ workspaceDir, provider, modelId })` — `workspaceDir` must already exist (omit for an auto temp dir); the model reference is the `(provider, modelId)` pair, so pass both to pick a configured model or neither for the project default — passing one alone throws. - The `approve` callback gates every tool call; **omitting it denies everything**. +- `opts.thinkingLevel` (`"none" | "low" | "medium" | "high" | "xhigh"`) overrides the agent's default (`model.thinking_level` in `system_config.yaml`) for this turn only — raise it for hard questions, drop it for latency-sensitive calls like titling or classification. +- Session lifetime is the app's memory model: reuse one Session for a stateful chat (context accumulates, as above), create one per request for stateless QA (the RAG recipe below); either way call `session.dispose()` when done to release background processes. - An Agent's behavior is edited in its `agent_state/` files (system_config.yaml, AGENTS.md, skills/), not in code. -- Call `session.dispose()` when done to release background processes. + +## Thinking and image messages + +Modern models think before answering and accept images; the stream and the input protocol carry both — use them instead of flattening everything to text. + +**Thinking (CoT) out.** Reasoning models stream `partial_thinking` (field `thinking`) before any `partial_text`, and a complete `thinking` message follows. Show the stream — a silent 20-second wait reads as a hang — but keep it in its own channel: a collapsible muted block per the web-design skill, auto-collapsed once answer text starts. Never concatenate thinking into the answer, store it as the answer, or cite from it; ignore its `fidelity` field (core's replay bookkeeping). Non-reasoning models simply never emit it — don't reserve UI space. + +**Images in.** Build image input with `imageUrlMessage` (a web URL or a base64 data URL) beside `userText` in the same `run` input: + +```ts +import { imageUrlMessage, userText } from "@prismshadow/penguin-core"; +session.run([userText(question), ...images.map(imageUrlMessage)], { ... }); +``` + +Browser flow: `` plus paste/drag-drop → `FileReader.readAsDataURL` → POST `{ question, images: [dataUrl] }` → the server maps each entry to `imageUrlMessage`. Reject non-image MIME types and cap size (a data URL rides the context window; a few MB is plenty). Whether the session model actually sees pixels is the model config's `vision` flag (`penguin config model list` prints `vision=Y/-`; set via `--vision/--no-vision` on `model add`, default supported): with `vision=false` the core folds the image into an `[attached image: ]` line and the built-in image tools read it through the project's configured `vision_model` (`penguin config model vision --provider --model-id --root `) — the app still works, through a description instead of direct sight. + +**Other payloads worth handling** (always narrow with the guards first): `partial_tool_call` / `partial_tool_call_output` — surface as an activity line ("running `search`…") in apps that grant tools; `request_end` (event) — a non-`completed` `status` is the error signal (`auth` → ask for a key; `message` carries the failure detail; `retry_in_ms` announces a planned in-run retry, renderable as a countdown); `token_usage` (event) — session-cumulative and last-request counts, if the app shows cost; `compaction_begin` / `compaction_end` (events) — long-lived chats only, show a brief "context being compacted" notice. Everything else is safe to ignore. ## RAG knowledge app @@ -270,12 +286,15 @@ http.createServer(async (req, res) => { } res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache" }); try { - const prompt = `Answer from the context below; cite blocks inline as [1][2]. If the context is not enough, say so.\n\n${context}\n\nQuestion: ${question}`; + const prompt = `Answer in plain text (no Markdown; short paragraphs) from the context below; cite blocks inline as [1][2]. If the context is not enough, say so.\n\n${context}\n\nQuestion: ${question}`; for await (const msg of session.run([userText(prompt)], { approve: async () => "deny", signal: ac.signal })) { if (isModelMessage(msg)) { const p = msg.payload; if (p.type === "partial_text" && p.event_type === "delta" && !res.writableEnded) res.write(`data: ${JSON.stringify({ delta: p.text })}\n\n`); + // Reasoning models: forward CoT on its own SSE field so the UI can collapse it. + if (p.type === "partial_thinking" && p.event_type === "delta" && !res.writableEnded) + res.write(`data: ${JSON.stringify({ thinking: p.thinking })}\n\n`); } } // Sources carry the matched chunk text verbatim: the UI must be able to show the exact @@ -311,12 +330,17 @@ http.createServer(async (req, res) => { }).listen(Number(process.env.PORT ?? 4630), () => console.log("http://localhost:4630")); ``` -**UI** (`public/index.html`) — a chat interface built per the web-design skill: message list, streamed assistant text appended delta by delta, the final `sources` event rendered as citation chips, an empty state inviting the first question with **3–4 example questions the corpus can actually answer** (pill chips; clicking one submits it), and a visible error state when `/api/ask` fails. Citations must satisfy both of these, never bare text: +**UI** (`public/index.html`) — a chat interface built per the web-design skill: message list, streamed assistant text appended delta by delta (plain text under the output contract below: escape, split blank-line paragraphs, style the `[n]` markers), `thinking` events into the collapsible reasoning block (collapse it when the first answer delta arrives), the final `sources` event rendered as citations (pill chips or accordion source cards), an empty state inviting the first question with **3–4 example questions the corpus can actually answer** (clicking one submits it), and a visible error state when `/api/ask` fails. Citations must satisfy both of these, never bare text: - **Reveal the original chunk**: clicking a citation chip (or an inline `[n]`) opens a popover/panel showing the matched chunk's `text` from the sources event **verbatim** — the numbering maps 1:1 to the context blocks in the prompt, so `[n]` always reveals exactly the block the answer drew on. - **Link to the real document**: inside the popover, `` using the `url` field (`/corpus/`, which this server serves) — clicking the chip itself opens the popover, the document link lives within it. When the corpus was cloned from a public repository, prefer mapping the path to the canonical upstream page instead (e.g. the GitHub blob URL derived from the clone URL). -**Persona** (`persona.md`) — the embedded agent's role, written per the agent-creation skill. Shape: one role sentence ("You are an expert on X; you answer strictly from the provided context blocks"), citation and refusal rules, answer language follows the question. +**Output format and language** — settle both up front, in the persona and the retriever, not in the UI: + +- **No Markdown pipeline — set the output format instead**: instruct the embedded agent (in `persona.md` and the per-request prompt) to answer in plain text — short paragraphs separated by blank lines, citations as bare `[n]`, no Markdown syntax. The UI then only escapes the text, splits paragraphs and styles the `[n]` markers; there is no renderer to build. When richer structure genuinely matters, have the model emit a small whitelisted HTML subset (`

  • `) and sanitize to exactly that whitelist before inserting — never inject unsanitized model output. +- **Cross-language retrieval**: the corpus and the user often speak different languages (English docs, Chinese questions), and BM25 is purely lexical — a Chinese question scores zero against English chunks. At ingest time derive a small bilingual keyword map for the corpus's core vocabulary (10–20 domain terms, e.g. `权限 → permissions / allow / deny`, `钩子 → hooks`) and expand query tokens through it in `search()` before scoring; keep the per-character CJK tokenizer. The persona already pins the answer language to the question's language. + +**Persona** (`persona.md`) — the embedded agent's role, written per the agent-creation skill. Shape: one role sentence ("You are an expert on X; you answer strictly from the provided context blocks"), citation and refusal rules, plain-text output (no Markdown — the output contract above), answer language follows the question. ## Verify before you hand over @@ -330,4 +354,4 @@ Never declare the app done without running it: Then `curl` one of the returned source `url`s — it must return the document, not a 404 (citation links have to resolve). 5. Open the UI (or screenshot it) to confirm the layout renders. -Fix any failure and re-verify. Report with backtick-wrapped relative paths (`server.ts`, `public/index.html`, …), how to start the app, and the assumptions you made. +Fix any failure and re-verify; when the app accepts image input, one verification question must include a real image. Report with backtick-wrapped relative paths (`server.ts`, `public/index.html`, …), how to start the app, and the assumptions you made. diff --git a/packages/skills/skills/web-design/SKILL.md b/packages/skills/skills/web-design/SKILL.md index 4a9b087..2c2584a 100644 --- a/packages/skills/skills/web-design/SKILL.md +++ b/packages/skills/skills/web-design/SKILL.md @@ -1,22 +1,33 @@ --- name: web-design -description: Penguin visual language for generated web pages and app UIs — GitHub-style simplicity with a single blue accent, light and pure-black dark themes, design tokens, and component and chat-interface recipes. +description: Penguin visual language for generated web pages and app UIs — GitHub-style simplicity with a single blue accent, light and pure-black dark themes, design tokens, component and chat-interface recipes, plus an opt-in warm paper editorial theme. short_description: Penguin-style visual defaults for generated web UIs. short_description_zh: 生成网页的 Penguin 风格视觉规范。 -version: 3 -updated: 2026-07-20T15:00:00Z +version: 6 +updated: 2026-07-30T11:10:00Z --- # Web Design -Default visual language for every web page or frontend you generate, distilled from the Penguin Harness landing page and web app. The idea is **GitHub-style simplicity**: solid backgrounds, 1px borders instead of shadows, system fonts, one blue accent used sparingly. Depth comes from hairline borders, not shadows or gradients; dark mode is pure black, not navy. Apply these defaults unless the user explicitly asks for another style. +Default visual language for every web page or frontend you generate, distilled from the Penguin Harness landing page and web app. The idea is **GitHub-style simplicity**: solid backgrounds, 1px borders instead of shadows, system fonts, one blue accent used sparingly. Depth comes from hairline borders, not shadows or gradients; dark mode is pure black, not navy. Apply these defaults unless the user explicitly asks for another style. One packaged alternative exists — the **paper editorial** theme below, for requests that call for a warm, print-like feel; pick one language per product and never mix them. The typography discipline, language, motion, IME, citation and responsiveness rules apply under both. Treat the user's one-line request as the whole spec: this skill fills every unstated gap, so the result is a finished page — never a wireframe that waits for styling feedback. ## Before you start -If the user's message only invokes this skill (e.g. "use web-design skill") without a concrete page or interface to build, ask what they want to build. When a concrete build is already requested (an app UI, a landing page, a RAG chat interface), do **not** ask about styling — apply the defaults below. +If the user's message only invokes this skill (e.g. "use web-design skill") without a concrete page or interface to build, ask what they want to build. When a concrete build is already requested (an app UI, a landing page, a RAG chat interface), do **not** ask about styling — colors, fonts, spacing, radii and layout are all decided by the defaults below; the only question ever worth asking is *what to build*, never *how it should look*. Route by request shape: conversational or docs-QA → the chat/RAG layout; product or marketing → the page layout; tool-like apps → a sticky nav + panels composed from the components. Non-negotiable for ANY text input that sends on Enter: **never send while an IME composition is in progress** (check `event.isComposing`, falling back to `event.keyCode === 229`, on keydown). For Chinese/Japanese/Korean input methods, that Enter only confirms the composed text — auto-sending on it fires half-typed messages. Details in the composer recipe below. +## Ship complete + +Every delivery, even from a one-line request, includes all of this unasked: + +- `` matching the UI language; ``; a real ``. +- Dark mode wired and persisted when using the default language (the paper theme is light-only); single column under 640px; no horizontal scroll. +- Every async surface has designed loading, empty, error and success states — never a blank region or a silent failure. +- A working keyboard path: visible `:focus-visible`, Esc closes overlays (focus returning to the trigger), Enter submits (IME-safe as above). +- `alt` text on images, `aria-label` on icon-only buttons; tap targets ≥ 40px. +- Zero external requests: system fonts, inline or local CSS/JS, inline SVG icons — no CDN, no icon font, no analytics. + ## Design tokens ```css @@ -82,15 +93,37 @@ One easing everywhere: `var(--ease)`, durations 120–280ms. Entrances rise in ( @media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } } ``` +## Opt-in theme: paper editorial + +A second complete language — warm paper tones, serif display headings, mono micro-labels — for when the user asks for a warm, editorial, print- or magazine-like feel. Light-only: if the product needs dark mode, use the default language instead. + +```css +:root { + color-scheme: light; + --paper: #f7f4ee; /* page bg */ --panel: #eeebe4; /* side panels */ --card: #fffdf9; + --ink: #262421; --muted: #716d67; --line: #ded9d0; /* warm hairlines */ + --accent: #d6663f; --accent-deep: #9e462b; /* the only accent; deep tone for text/links */ + --live: #547567; /* status green — live dots and retrieval notes only */ +} +``` + +- **Serif display over sans body** — hero and panel titles use system serifs (`Georgia, "Times New Roman", "Songti SC", "Noto Serif SC", serif`; still no CDN fonts), weight 400–500, `letter-spacing: -.04em`, hero `clamp(38px, 5vw, 62px)`, with exactly one word wrapped in an accent-colored `<em>`. Body text stays on the default sans stack, 13–14px / line-height 1.7. +- **Mono micro-labels** — eyebrows/kickers, example numbering (`01`), status values and citation numbers: 9–11px uppercase monospace, `letter-spacing: .1–.18em`, in `--accent-deep` or `--muted`. The serif-vs-mono contrast is this theme's hierarchy tool, replacing the default theme's weight-and-size ladder. +- **Tighter radii, warm shadows** — cards, buttons and source rows use 4–5px radii (pills stay 9999px). Unlike the default language, soft warm-tinted shadows belong here: composer `box-shadow: 0 14px 40px rgb(65 50 39 / .09)`; example-card hover may lift `translateY(-2px)` and gain `0 10px 28px rgb(62 49 40 / .07)`. +- **Signature shapes** — brand mark: an `--accent` square with one tight corner (`border-radius: 11px 11px 11px 4px`) holding a white serif initial; user chat bubbles echo it with `border-radius: 3px 14px 14px 14px`. Empty-state flourish: that mark in a circular medallion ringed by 1–2 offset half-circle hairlines (`clip-path: inset(0 50% 0 0)`) — this theme's counterpart of the default dot grid. +- **Buttons** — primary is an `--accent` fill (hover `--accent-deep`; icon-only send buttons go circular); secondary stays a hairline border on `--card`/white. Links use `--accent-deep`. + ## Chat / RAG app layout The default shape for a generated conversational or docs-QA app: -- **Shell** — centered column, `max-width: 48rem`, `padding: 0 16px`; sticky nav on top with the app name; message list grows, composer pinned at the bottom. -- **Empty state** — vertically centered title + one-line subtitle in `--fg-muted`, over an optional dot-grid backdrop (`background-image: radial-gradient(rgb(26 115 232 / .14) 1px, transparent 1px); background-size: 22px 22px;` faded out with a bottom mask) — the only decorative flourish allowed. Below it, a wrapped row of 3–4 example-question pill chips the app can genuinely answer; clicking one fills and submits the composer. -- **Messages** — user messages right-aligned in a `--gray-100`/dark `#1f1f1f` rounded bubble (radius 12px, padding 8px 14px, max-width 85%); assistant messages plain on the page background, no bubble. Stream deltas into the assistant message as they arrive with a 1-character pulsing cursor; render markdown. -- **Citations** — after an answer, a wrapped row of pill chips: `[1] path — heading`, brand-tinted variant. Clicking a chip (or an inline `[n]` in the answer) opens a popover/panel showing the **verbatim original text block** the citation refers to, with a link to open the full source document; a citation that is only a label or only a link is not enough. -- **Composer** — a bordered card (radius 12px) with a borderless textarea inside and a small primary send button bottom-right; Enter sends, Shift+Enter for newline; disable while streaming. **Never send while an IME composition is in progress**: on keydown, ignore Enter when `event.isComposing` (or `event.keyCode === 229`) — for CJK input methods that Enter only confirms the composed text, and auto-sending on it fires half-typed messages. +- **Shell** — centered column, `max-width: 48rem`, `padding: 0 16px`; sticky nav on top with the app name; message list grows, composer pinned at the bottom. A docs-QA app whose index is worth showing may add a left **knowledge panel** (grid `310px 1fr` under the nav; panel-toned bg `--gray-50` / paper `--panel`, 1px right border): kicker, display title, one-paragraph description, an index-status block of label-vs-mono-value rows (docs, chunks, last synced, a LIVE dot), and a one-line pipeline (`corpus → retrieval → cited answer`); the chat pane keeps its own centered column. On mobile the panel hides behind an ⓘ button in the nav and drops down fixed beneath it. +- **Empty state** — a vertically centered composition: uppercase eyebrow (brand / `--accent-deep`) → title with at most one accent word → one-line subtitle in `--fg-muted`, over the theme flourish (default: dot-grid backdrop `background-image: radial-gradient(rgb(26 115 232 / .14) 1px, transparent 1px); background-size: 22px 22px;` faded out with a bottom mask; paper: the orbit medallion) — the only decorative flourish allowed. Below it, 3–4 example questions the app can genuinely answer, as pill chips or as a 2-column grid (1-column mobile) of numbered cards — mono `01` in accent, the question at 13px, a `↗` corner affordance; hover tints the border (paper may also lift 2px). Clicking one fills and submits the composer. +- **Messages** — user messages right-aligned in a `--gray-100`/dark `#1f1f1f` rounded bubble (radius 12px, padding 8px 14px, max-width 85%); assistant messages plain on the page background, no bubble. Stream deltas into the assistant message as they arrive with a 1-character pulsing cursor. Prefer a plain-text output contract over a Markdown pipeline: when the app controls its model's prompt, instruct plain-text answers and style them directly (escape → blank-line paragraphs → decorate `[n]` markers); build a Markdown renderer only when the output format isn't yours to set, and then escape HTML in the model text before your own transforms — never inject it raw. Retrieval-backed answers may open with a small status line above the text — breathing dot + "已检索 N 个相关片段" / "N sources matched", 11px in `--live`/`--fg-faint` — so evidence visibly precedes prose. +- **Thinking** — reasoning models may stream a chain of thought before the answer. Give it its own collapsible block above the answer text: a header row ("思考过程" / "Thinking" + chevron + elapsed seconds) over 13px `--fg-muted` content behind a hairline left border, expanded while it streams, auto-collapsed the moment the first answer delta arrives (reopenable). Never restyle thinking as answer prose and never cite from it; when the model emits none, no placeholder space appears. +- **Citations** — inline `[n]` markers render as small raised accent superscripts (mono, ~10px). After the answer, either surface works, but it must reveal both the **verbatim original text block** and a link to the full source — a citation that is only a label or only a link is not enough: (a) a wrapped row of brand-tinted pill chips `[1] path — heading` opening a popover/panel on click; or (b) an **accordion of source cards** under the answer — each row a tinted numbered circle + doc title + section + `+/−` chevron, expanding to the verbatim excerpt as a mono blockquote (accent left border, max-height ≈150px, scrollable) plus the source link. +- **Composer** — a bordered card (radius 12px; paper: 4–5px with the warm shadow) holding a borderless textarea and a footer row: kbd hints on the left (`Enter` send · `Shift+Enter` newline, 11px muted, hidden on mobile), the primary send button on the right (paper: circular accent icon button); Enter sends, Shift+Enter for newline; disable while streaming. A short gradient from transparent into the page bg eases the list into the composer zone; below it, one 10–11px muted disclaimer line says what answers are based on. **Never send while an IME composition is in progress**: on keydown, ignore Enter when `event.isComposing` (or `event.keyCode === 229`) — for CJK input methods that Enter only confirms the composed text, and auto-sending on it fires half-typed messages. +- **Attachments** (when the app accepts images) — a paperclip button plus paste and drag-drop onto the composer; queued images preview above the textarea as 48px rounded thumbnails with a hover `×`; send attaches and clears the queue. In sent user messages, thumbnails render at max-height ~160px, radius 8px, click to view full size. - **States** — loading: three pulsing dots in `--fg-faint`; error: 13px `#b91c1c` text on `#fef2f2` (dark: `#f87171` on `#450a0a`) in a rounded box with a retry affordance. Never leave a silent failure. ## Page layout (marketing / landing) diff --git a/packages/web/src/lib/strings-en.ts b/packages/web/src/lib/strings-en.ts index ace1242..f0aba8e 100644 --- a/packages/web/src/lib/strings-en.ts +++ b/packages/web/src/lib/strings-en.ts @@ -590,7 +590,7 @@ export const en: Strings = { ## Index page - \`index.html\` at the root: a card grid listing all 10 games (name + one-line mechanic + controls), each card opening its game. -- One design language shared with every game, following the web-design skill; dark/light themes via \`<html data-theme>\` remembered in localStorage; responsive, single column on phones. +- One design language shared with every game, following the web-design skill. ## Wrap-up - Review as a whole: the 10 mechanics really are distinct, the styling is consistent, and every index link resolves. @@ -622,7 +622,7 @@ export const en: Strings = { - Keyboard shortcuts: Space play/pause, ← → previous/next, ↑ ↓ volume ## Design -Penguin visual style (see the web-design skill), dark/light themes via <html data-theme>, dark by default, remembered in localStorage. Responsive: on phones the sidebar becomes a horizontally scrolling top bar. +Penguin visual style (see the web-design skill), dark by default. On phones the sidebar becomes a horizontally scrolling top bar. When done, open index.html in a browser and self-test once.`, }, @@ -635,12 +635,7 @@ When done, open index.html in a browser and self-test once.`, "the app acts as a Claude Code configuration expert, answering Claude Code questions " + "with retrieval-augmented replies and clickable citations that reveal the matched " + "original text chunk and link to the real documents; " + - "give it a beautiful web chat UI following the web-design skill, with a few example questions in the empty state. " + - "Pay particular attention to matching the question's language against the corpus: the docs are English, " + - "while questions will often be Chinese. With a lexical retriever such as BM25, a Chinese query MUST be " + - "converted to English first (translate it, or extract English keywords) before it reaches the index — " + - "otherwise not a single Chinese term matches the English index and retrieval silently degrades to nothing. " + - "Mixed Chinese/English questions must retrieve correctly too, and the answer should follow the language of the question. " + + "give it a beautiful web chat UI following the web-design skill. " + "When done, run the app and self-test one Chinese question and one English question, confirming both retrieve " + "the right English documents and stream their answers, then tell me how to access it.", }, diff --git a/packages/web/src/lib/strings.ts b/packages/web/src/lib/strings.ts index 7f93e5a..5e977a2 100644 --- a/packages/web/src/lib/strings.ts +++ b/packages/web/src/lib/strings.ts @@ -574,7 +574,7 @@ export const zh = { ## 索引首页 - 根目录 \`index.html\`:卡片网格列出全部 10 个游戏(名称 + 一句话玩法 + 操作方式),点击进入对应游戏。 -- 与所有游戏共用一套设计语言,遵循 web-design 技能;深色 / 浅色主题(\`<html data-theme>\`)并用 localStorage 记忆;响应式,手机端单列。 +- 与所有游戏共用一套设计语言,遵循 web-design 技能。 ## 收尾 - 统一验收:10 个游戏玩法确实不重复、风格一致,索引页的链接全部可达。 @@ -606,7 +606,7 @@ export const zh = { - 键盘快捷键:空格播放暂停、← → 切歌、↑ ↓ 调音量 ## 设计 -Penguin 视觉风格(见 web-design 技能),深色/浅色主题(<html data-theme>),默认深色,localStorage 记忆。响应式:手机端侧边栏变为顶部横向滚动。 +Penguin 视觉风格(见 web-design 技能),默认深色。手机端侧边栏变为顶部横向滚动。 完成后在浏览器打开 index.html 自测一次。`, }, @@ -618,11 +618,7 @@ Penguin 视觉风格(见 web-design 技能),深色/浅色主题(<html da "克隆仓库并整理语料,建立检索索引;应用化身 Claude Code 配置专家," + "检索增强回答 Claude Code 相关问题并标注可点击的来源引用——" + "引用要能展示命中的原文片段,并链接到真实文档;" + - "按 web-design 技能提供美观的 Web 聊天界面,空态展示几个示例问题。" + - "特别注意提问与语料的语言匹配:语料是英文,提问很可能是中文。" + - "如果用 BM25 之类的词法检索,中文查询必须先转换成英文(翻译或抽取英文关键词)再进入检索," + - "否则中文词在英文索引里一个都命不中,会静默退化成空召回;" + - "中英混合提问同样要能正确召回,且回答语言跟随提问语言。" + + "按 web-design 技能提供美观的 Web 聊天界面。" + "完成后运行应用,用一个中文问题和一个英文问题各自测一次," + "确认两者都检索到了正确的英文文档、流式回答正常,并告诉我访问方式。", },