diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7f0567668bf..74797ebb98d 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -38,6 +38,7 @@ Each package includes a DESIGN.md file, read that to gain a general understandin - `examples/design-system/` — Shared semantic design tokens (`@microsoft/fast-examples-design-system`) consumed by all example apps. - `examples/csr/todo-app/` — A simple To-Do app demonstrating FAST usage patterns and using the shared `@microsoft/fast-examples-design-system` tokens. - `examples/csr/todo-mobx-app/` — A To-Do app demonstrating how to integrate MobX state with `@microsoft/fast-element` using a single `autorun` per component (no custom bridge code). +- `examples/ssr/chat-app/` — A declarative FAST chat demo that pre-renders with `@microsoft/fast-build` and streams canned assistant replies into the transcript. Dark theme. ## Skills diff --git a/examples/DESIGN.md b/examples/DESIGN.md index 9d908241f3a..b0e7ffac682 100644 --- a/examples/DESIGN.md +++ b/examples/DESIGN.md @@ -1,7 +1,8 @@ # FAST example apps — design guidance This document is the workspace-level guide for building UI in `examples/*` -and its rendering-strategy subfolders (currently `examples/csr/`). It +and its rendering-strategy subfolders (`examples/csr/` and `examples/ssr/`). +It explains how the apps share a visual language, the constraints every example must follow, and the rules that humans and coding agents should apply when making changes. The token vocabulary itself lives in @@ -157,8 +158,9 @@ document.documentElement.removeAttribute("data-theme"); // restore system ``` For an intentionally single-theme app — like -[`todo-app`](./todo-app/) — hard-code the attribute in markup -(``) and never touch it from JavaScript. +[`csr/todo-app`](./csr/todo-app/) and [`ssr/chat-app`](./ssr/chat-app/) — +hard-code the attribute in markup (`` or +``) and never touch it from JavaScript. ## Authoring rules diff --git a/examples/README.md b/examples/README.md index dbc01138c1e..20394a851eb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -12,10 +12,11 @@ FAST patterns in a complete app context. | `design-system` | `@microsoft/fast-examples-design-system` | Shared CSS design tokens (no JS) consumed by every example app. | | `csr/todo-app` | `@microsoft/fast-todo-app-example` | A To-Do app demonstrating `@microsoft/fast-element` patterns end to end, styled with the shared design-system tokens (light theme, no runtime toggle). | | `csr/todo-mobx-app` | `@microsoft/fast-todo-mobx-app-example` | A To-Do app showing how to integrate MobX state with `@microsoft/fast-element` using a single `autorun` per component (no custom bridge code), styled with the shared design-system tokens. | +| `ssr/chat-app` | `@microsoft/fast-chat-app-example` | A declarative FAST chat demo that pre-renders with `@microsoft/fast-build` and streams canned assistant replies into the transcript, styled with the shared design-system tokens (dark theme, no runtime toggle). | Example apps are grouped by rendering strategy. Client-side-rendered (CSR) -apps live under [`examples/csr/`](./csr/); future server-side-rendered (SSR) -apps will live alongside them under their own subfolder. +apps live under [`examples/csr/`](./csr/) and server-side-rendered (SSR) apps +live under [`examples/ssr/`](./ssr/). ## Shared design system @@ -51,17 +52,19 @@ See: ## Creating a new example app 1. Scaffold a new folder under the appropriate rendering-strategy subfolder - (CSR apps go under `examples/csr//`). -2. Use [`examples/csr/todo-app/`](./csr/todo-app/) as a reference for - `package.json`, `tsconfig.json`, `vite.config.ts`, and `index.html`. -3. Add `"@microsoft/fast-examples-design-system": "workspace:*"` to the new + (CSR apps go under `examples/csr//`; SSR apps go under + `examples/ssr//`). +2. Use [`examples/csr/todo-app/`](./csr/todo-app/) as a reference for CSR + apps, or [`examples/ssr/chat-app/`](./ssr/chat-app/) as a reference for + SSR apps (`package.json`, `tsconfig.json`, build config, entry HTML). +3. Add `"@microsoft/fast-examples-design-system": "^1.0.0"` to the new app's `dependencies` and run `npm install` from the repo root. 4. Import `@microsoft/fast-examples-design-system/tokens.css` exactly once at - the app entry point (typically `src/main.ts`). For an intentionally - single-theme app, hard-code `` (or `"dark"`) - in `index.html` and never touch it from JavaScript. + the app entry point (typically `src/main.ts` or `src/index.ts`). For an + intentionally single-theme app, hard-code `` (or + `"dark"`) in the entry HTML and never touch it from JavaScript. 5. Reference tokens via `var(--fast-...)` in component `css` template - literals — do not hard-code design values. + literals or `.css` files — do not hard-code design values. 6. If the app needs a theme toggle, set or remove the `data-theme` attribute on `` from your app code: ```ts @@ -86,6 +89,7 @@ For the existing example apps: ```shell npm start -w @microsoft/fast-todo-app-example npm start -w @microsoft/fast-todo-mobx-app-example +npm start -w @microsoft/fast-chat-app-example ``` ## Useful links diff --git a/examples/ssr/chat-app/.gitignore b/examples/ssr/chat-app/.gitignore new file mode 100644 index 00000000000..070d1ee9985 --- /dev/null +++ b/examples/ssr/chat-app/.gitignore @@ -0,0 +1,2 @@ +# Generated by build-markup.mjs from entry.html + state.json + templates.html +index.html diff --git a/examples/ssr/chat-app/DESIGN.md b/examples/ssr/chat-app/DESIGN.md new file mode 100644 index 00000000000..407b2850ac4 --- /dev/null +++ b/examples/ssr/chat-app/DESIGN.md @@ -0,0 +1,168 @@ +# Chat App Example Design + +## Overview + +`examples/ssr/chat-app` is a small end-to-end FAST example that combines three ideas: + +1. **Declarative FAST** custom elements defined with `RenderableFASTElement` from `@microsoft/fast-html` and matching `` definitions. +2. **Initial markup generated by `@microsoft/fast-build`** from an entry document, state file, and declarative `` definitions. +3. **Streaming assistant replies** with the hidden-iframe + `document.write()` technique described in Jake Archibald's article, “Fun hacks for faster content” (). + +The example intentionally keeps the conversation simple and deterministic so the streaming mechanism is easy to observe. + +## Design system + +This example consumes the shared `@microsoft/fast-examples-design-system` +workspace package. + +- `src/main.ts` imports `@microsoft/fast-examples-design-system/tokens.css` + before hydration and custom-element definition so the tokens are registered + before any component renders. +- Every design value in `public/styles.css` is referenced through + `var(--fast-...)`; colors, type sizes and line heights, spacing, corners, + stroke widths, shadows, durations, and curves are not hard-coded. +- The theme is hard-coded in markup with `` in + `entry.html`, and the FAST build step carries that attribute into generated + `index.html`. The example does not implement a runtime theme toggle. Apps + that want one can set, change, or remove the attribute with the standard DOM + API described in [`../../design-system/README.md`](../../design-system/README.md) + and [`../../design-system/DESIGN.md`](../../design-system/DESIGN.md). + +## Folder structure + +This example follows the same top-level shape as `examples/csr/todo-app/` and adds the extra declarative-rendering source files needed for FAST build: + +- `package.json` — workspace scripts for build/start/test. +- `tsconfig.json` — copied from `todo-app` with the example-specific LSP route. +- `vite.config.ts` — same shape as `todo-app`, building into `www/`. +- `entry.html` — source HTML passed to `@microsoft/fast-build`; hard-codes `data-theme="dark"`. +- `state.json` — initial SSR state for the shell. +- `templates.html` — declarative `` definitions for every custom element. +- `build-markup.mjs` — regenerates `index.html` and injects `` definitions for hydration. +- `index.html` — generated output consumed by Vite. +- `public/styles.css` — shared token-based stylesheet loaded by the page and every component shadow root. +- `src/chat-app.ts` — root element, queueing, exact-match lookup, and iframe streaming logic. +- `src/chat-data.ts` — canned turn data. +- `src/chat-message.ts` — message bubble custom element. +- `src/chat-card.ts` — supporting card callout custom element. +- `src/chat-suggestion.ts` — copyable/clickable next-prompt custom element. +- `src/design-system.d.ts` — declares the shared `tokens.css` side-effect import for TypeScript. +- `src/main.ts` — imports the shared tokens, enables hydration, and defines the custom elements. +- `src/exports.ts` — barrel for the example runtime. + +## Build flow + +`npm run build` for this example performs three steps: + +1. `npm run build:markup` + - runs the `@microsoft/fast-build` CLI against `fast-build.config.json` + - renders `entry.html` + `state.json` + `templates.html` into `index.html` + - injects the raw `` definitions before the module script so the `` runtime can resolve them during hydration +2. `tsgo -p tsconfig.json` + - type-checks and emits the TypeScript source into `dist/` +3. `vite build` + - bundles the hydrated browser entry into `www/` + +## Runtime flow + +### Hydration and declarative templates + +`src/main.ts` imports the shared design tokens, side-effect-imports each +component class, and triggers hydration by registering the `` +custom element via `@microsoft/fast-html`'s `TemplateElement`: + +- `chat-message` +- `chat-card` +- `chat-suggestion` +- `chat-app` + +Each class extends `RenderableFASTElement(FASTElement)`, so its rendered +shadow root is hydrated against the matching `` in +`templates.html`. + +### Conversation model + +`src/chat-data.ts` stores six canned turns. Each turn has: + +- the **exact** user message that must be entered (comparison uses `trim()` only) +- the bot's full HTML response (`responseHtml`) +- the same response broken into visible streaming chunks (`responseChunks`) +- the next suggested user message shown after the reply + +Unknown input maps to a single fallback turn. + +### Queueing + +The textarea stays enabled while a reply is streaming. To keep the conversation stable, `chat-app` uses a FIFO queue: + +1. submit message +2. append the user bubble immediately +3. look up the canned bot turn (or fallback) +4. queue the bot turn +5. process one bot turn at a time + +This lets the user queue the next line while the current assistant turn is still streaming. + +### Streaming via iframe + `document.write` + +The implementation follows the article's approach closely: + +1. create a hidden iframe +2. wait for it to load +3. write a dummy `` tag into the iframe document +4. move that element into the real chat transcript +5. continue calling `iframe.contentDocument.write(...)` asynchronously for each HTML chunk +6. close the streaming element and the iframe document when done + +Because the HTML parser keeps the open element on its stack, later `document.write()` calls continue filling the moved element even after it has been appended into the transcript. + +The example adds a small artificial delay between chunks so the streaming is obvious during a quick manual smoke test. + +## Custom elements + +### `` + +The root shell element. It renders: + +- the title and intro copy +- the transcript container +- the textarea composer +- the initial assistant prompt and first suggestion + +It also owns: + +- exact-match response lookup +- submit handling +- Enter vs. Shift+Enter behavior +- streaming queue management +- suggestion click handling + +### `` + +A simple bubble wrapper with a slot. `kind="bot"` and `kind="user"` switch the visual treatment. + +### `` + +A small callout card used inside every assistant turn so each reply mixes native HTML with at least one FAST custom element. + +### `` + +Renders the next suggested user prompt as selectable text and emits a `use-suggestion` event when clicked so `chat-app` can paste it into the textarea. + +## Canned conversation data + +| User message | Next suggested message | +| --- | --- | +| `Hi` | `How are you?` | +| `How are you?` | `What's the weather like?` | +| `What's the weather like?` | `Tell me a joke` | +| `Tell me a joke` | `What can you do?` | +| `What can you do?` | `Goodbye` | +| `Goodbye` | `Hi` | +| fallback | `Hi` | + +## Notable decisions + +- The initial welcome bubble is static shell content rather than part of the exact-match turn table, so the canned conversation can start from the first user message. +- All page and component styling lives in a single external `public/styles.css` file. It is linked from the document `` (render-blocking, so the bytes are loaded before the rest of the body is processed) and from inside every `