From 13a31dc834aebce20703e2b8e92ac3004c9f550d Mon Sep 17 00:00:00 2001 From: Jane Chu Date: Thu, 21 May 2026 11:55:07 -0700 Subject: [PATCH] feat: add chat-app and webui-todo-app SSR examples with design tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add examples/ssr/chat-app/ — declarative FAST chat demo using @microsoft/fast-build for SSR pre-rendering and hidden-iframe streaming for assistant replies. Uses the dark theme statically. - Add examples/ssr/webui-todo-app/ — todo app using @microsoft/webui for server-side prerendering and @microsoft/fast-html for hydration. Uses the light theme statically. - Both apps consume @microsoft/fast-examples-design-system tokens via CSS custom properties. No hard-coded design values; no runtime theme switching. - Add 'examples/ssr/*' to the root workspaces config and update the workspace-level README.md, DESIGN.md, and .github/copilot-instructions to describe the new ssr/ subfolder. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 7 + examples/DESIGN.md | 15 +- examples/README.md | 23 +- examples/ssr/chat-app/.gitignore | 2 + examples/ssr/chat-app/DESIGN.md | 164 +++++ examples/ssr/chat-app/README.md | 69 ++ examples/ssr/chat-app/build-markup.mjs | 36 + examples/ssr/chat-app/entry.html | 16 + examples/ssr/chat-app/fast-build.config.json | 6 + examples/ssr/chat-app/package.json | 33 + examples/ssr/chat-app/public/styles.css | 323 +++++++++ examples/ssr/chat-app/src/chat-app.ts | 202 ++++++ examples/ssr/chat-app/src/chat-card.ts | 16 + examples/ssr/chat-app/src/chat-data.ts | 86 +++ examples/ssr/chat-app/src/chat-message.ts | 16 + examples/ssr/chat-app/src/chat-suggestion.ts | 20 + examples/ssr/chat-app/src/design-system.d.ts | 1 + examples/ssr/chat-app/src/exports.ts | 5 + examples/ssr/chat-app/src/main.ts | 14 + examples/ssr/chat-app/state.json | 5 + examples/ssr/chat-app/templates.html | 84 +++ examples/ssr/chat-app/tsconfig.json | 19 + examples/ssr/chat-app/vite.config.ts | 13 + examples/ssr/webui-todo-app/DESIGN.md | 142 ++++ examples/ssr/webui-todo-app/README.md | 124 ++++ examples/ssr/webui-todo-app/data/state.json | 23 + examples/ssr/webui-todo-app/package.json | 36 + examples/ssr/webui-todo-app/src/index.html | 30 + examples/ssr/webui-todo-app/src/index.ts | 36 + .../webui-todo-app/src/todo-app/todo-app.css | 75 ++ .../webui-todo-app/src/todo-app/todo-app.html | 29 + .../webui-todo-app/src/todo-app/todo-app.ts | 99 +++ .../src/todo-item/todo-item.css | 71 ++ .../src/todo-item/todo-item.html | 11 + .../webui-todo-app/src/todo-item/todo-item.ts | 29 + examples/ssr/webui-todo-app/tsconfig.json | 16 + package-lock.json | 643 +++++++++++++++++- package.json | 3 +- 38 files changed, 2529 insertions(+), 13 deletions(-) create mode 100644 examples/ssr/chat-app/.gitignore create mode 100644 examples/ssr/chat-app/DESIGN.md create mode 100644 examples/ssr/chat-app/README.md create mode 100644 examples/ssr/chat-app/build-markup.mjs create mode 100644 examples/ssr/chat-app/entry.html create mode 100644 examples/ssr/chat-app/fast-build.config.json create mode 100644 examples/ssr/chat-app/package.json create mode 100644 examples/ssr/chat-app/public/styles.css create mode 100644 examples/ssr/chat-app/src/chat-app.ts create mode 100644 examples/ssr/chat-app/src/chat-card.ts create mode 100644 examples/ssr/chat-app/src/chat-data.ts create mode 100644 examples/ssr/chat-app/src/chat-message.ts create mode 100644 examples/ssr/chat-app/src/chat-suggestion.ts create mode 100644 examples/ssr/chat-app/src/design-system.d.ts create mode 100644 examples/ssr/chat-app/src/exports.ts create mode 100644 examples/ssr/chat-app/src/main.ts create mode 100644 examples/ssr/chat-app/state.json create mode 100644 examples/ssr/chat-app/templates.html create mode 100644 examples/ssr/chat-app/tsconfig.json create mode 100644 examples/ssr/chat-app/vite.config.ts create mode 100644 examples/ssr/webui-todo-app/DESIGN.md create mode 100644 examples/ssr/webui-todo-app/README.md create mode 100644 examples/ssr/webui-todo-app/data/state.json create mode 100644 examples/ssr/webui-todo-app/package.json create mode 100644 examples/ssr/webui-todo-app/src/index.html create mode 100644 examples/ssr/webui-todo-app/src/index.ts create mode 100644 examples/ssr/webui-todo-app/src/todo-app/todo-app.css create mode 100644 examples/ssr/webui-todo-app/src/todo-app/todo-app.html create mode 100644 examples/ssr/webui-todo-app/src/todo-app/todo-app.ts create mode 100644 examples/ssr/webui-todo-app/src/todo-item/todo-item.css create mode 100644 examples/ssr/webui-todo-app/src/todo-item/todo-item.html create mode 100644 examples/ssr/webui-todo-app/src/todo-item/todo-item.ts create mode 100644 examples/ssr/webui-todo-app/tsconfig.json diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 7f0567668bf..a1fd845644f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -35,9 +35,16 @@ Each package includes a DESIGN.md file, read that to gain a general understandin ### Example projects +Example apps are grouped by rendering strategy. Client-side-rendered (CSR) +apps live under `examples/csr/`; server-side-rendered (SSR) apps — including +those that pre-render their initial markup with `@microsoft/fast-build` or +`@microsoft/webui` — live under `examples/ssr/`. + - `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 chat app that pre-renders its shell with `@microsoft/fast-build` and streams canned assistant replies, styled with the shared design-system tokens (dark theme, no runtime toggle). +- `examples/ssr/webui-todo-app/` — A To-Do app that pre-renders with `@microsoft/webui` (`webui serve --plugin=fast`) and hydrates with `@microsoft/fast-html`, styled with the shared design-system tokens (light theme, no runtime toggle). ## Skills diff --git a/examples/DESIGN.md b/examples/DESIGN.md index 9d908241f3a..61b186bfdc2 100644 --- a/examples/DESIGN.md +++ b/examples/DESIGN.md @@ -1,7 +1,9 @@ # 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/` for client-side +rendered apps, `examples/ssr/` for apps whose initial markup is pre-rendered +on the server with `@microsoft/fast-build` or `@microsoft/webui`). 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 @@ -26,8 +28,8 @@ alongside this one. ## Goals -- Give every app in `examples/*` (across all rendering-strategy subfolders) - a shared, consistent visual language. +- Give every app in `examples/*` (across both `csr/` and `ssr/` rendering + strategies) a shared, consistent visual language. - Provide a single source of truth for shared visual tokens. - Stay framework-free and dependency-free by exposing CSS custom properties only. No theme runtime, no helper API, no component layer. @@ -157,8 +159,11 @@ 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/), [`csr/todo-mobx-app`](./csr/todo-mobx-app/), +or [`ssr/webui-todo-app`](./ssr/webui-todo-app/) (all light), and +[`ssr/chat-app`](./ssr/chat-app/) (dark) — 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..0dfb0de2655 100644 --- a/examples/README.md +++ b/examples/README.md @@ -12,10 +12,14 @@ 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 chat app whose initial shell is pre-rendered with `@microsoft/fast-build` and whose assistant replies stream into the transcript through a hidden iframe, styled with the shared design-system tokens (dark theme, no runtime toggle). | +| `ssr/webui-todo-app` | `@microsoft/fast-webui-todo-app-example` | A To-Do app pre-rendered with `@microsoft/webui` (`webui serve --plugin=fast`) and hydrated with `@microsoft/fast-html`, styled with the shared design-system tokens (light 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/). Server-side-rendered (SSR) apps — +those that pre-render their initial markup on the server with +`@microsoft/fast-build` or `@microsoft/webui` and then hydrate on the client — +live under [`examples/ssr/`](./ssr/). ## Shared design system @@ -50,10 +54,15 @@ 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`. +1. Scaffold a new folder under the appropriate rendering-strategy subfolder. + CSR apps go under `examples/csr//`; SSR apps (those that + pre-render their initial markup with `@microsoft/fast-build` or + `@microsoft/webui`) go under `examples/ssr//`. +2. Use [`examples/csr/todo-app/`](./csr/todo-app/) as a reference for CSR + apps. For SSR apps, use [`examples/ssr/chat-app/`](./ssr/chat-app/) when + pre-rendering with `@microsoft/fast-build` or + [`examples/ssr/webui-todo-app/`](./ssr/webui-todo-app/) when pre-rendering + with `@microsoft/webui`. 3. Add `"@microsoft/fast-examples-design-system": "workspace:*"` 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 @@ -86,6 +95,8 @@ 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 +npm start -w @microsoft/fast-webui-todo-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..b6e84f12877 --- /dev/null +++ b/examples/ssr/chat-app/DESIGN.md @@ -0,0 +1,164 @@ +# 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 `declarativeTemplate()`. +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 `declarativeTemplate()` 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, calls `enableHydration()`, then defines: + +- `chat-message` +- `chat-card` +- `chat-suggestion` +- `chat-app` + +Each class uses `template: declarativeTemplate()`, so the runtime resolves the matching `` from `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 `