Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 5 additions & 3 deletions examples/DESIGN.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
(`<html data-theme="light">`) 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 (`<html data-theme="light">` or
`<html data-theme="dark">`) and never touch it from JavaScript.

## Authoring rules

Expand Down
24 changes: 14 additions & 10 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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/<your-app>/`).
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/<your-app>/`; SSR apps go under
`examples/ssr/<your-app>/`).
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 `<html data-theme="light">` (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 `<html data-theme="light">` (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 `<html>` from your app code:
```ts
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions examples/ssr/chat-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Generated by build-markup.mjs from entry.html + state.json + templates.html
index.html
168 changes: 168 additions & 0 deletions examples/ssr/chat-app/DESIGN.md
Original file line number Diff line number Diff line change
@@ -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 `<f-template name="...">` definitions.
2. **Initial markup generated by `@microsoft/fast-build`** from an entry document, state file, and declarative `<f-template>` definitions.
3. **Streaming assistant replies** with the hidden-iframe + `document.write()` technique described in Jake Archibald's article, β€œFun hacks for faster content” (<https://jakearchibald.com/2016/fun-hacks-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 `<html data-theme="dark">` 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 `<f-template>` definitions for every custom element.
- `build-markup.mjs` β€” regenerates `index.html` and injects `<f-template>` 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 `<f-template>` definitions before the module script so the `<f-template>` 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 `<f-template>`
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 `<f-template name="...">` 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 `<streaming-element>` 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

### `<chat-app>`

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

### `<chat-message>`

A simple bubble wrapper with a slot. `kind="bot"` and `kind="user"` switch the visual treatment.

### `<chat-card>`

A small callout card used inside every assistant turn so each reply mixes native HTML with at least one FAST custom element.

### `<chat-suggestion>`

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 `<head>` (render-blocking, so the bytes are loaded before the rest of the body is processed) and from inside every `<template shadowrootmode="open">` in `templates.html`. The browser fetches the file once and applies it to each shadow root, so styles are isolated per component but maintained in one place. Each design value references the shared `--fast-*` tokens.
- Dynamic messages are appended at runtime with DOM APIs (and streamed bot HTML) while the overall shell and component authoring remain declarative FAST.
69 changes: 69 additions & 0 deletions examples/ssr/chat-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Chat App Example

A small declarative FAST chat demo that pre-renders its initial markup with `@microsoft/fast-build` and streams canned assistant replies with a hidden iframe plus `document.write()`.

## Design system

This example consumes the shared `@microsoft/fast-examples-design-system`
workspace package. The design-system package is **CSS-only** β€” `src/main.ts`
imports `@microsoft/fast-examples-design-system/tokens.css` once, and
`public/styles.css` references the registered `--fast-*` tokens through
`var(...)`.

The app uses the **dark** theme statically. `<html data-theme="dark">` is set
in [`entry.html`](./entry.html), so the FAST build step carries that attribute
into the generated `index.html`. The example does not implement a runtime
theme toggle.

For the shared package overview, the token catalog, and authoring rules, see
[`../../design-system/README.md`](../../design-system/README.md) and
[`../../design-system/DESIGN.md`](../../design-system/DESIGN.md).

## What it demonstrates

- declarative FAST custom elements via `RenderableFASTElement` + `@microsoft/fast-html` `<f-template>`s
- initial markup rendered from `entry.html` + `templates.html`
- chunked assistant response streaming with the Jake Archibald iframe technique
- mixed native HTML and FAST custom elements in each canned reply
- exact-match canned prompts with a fallback response

## Suggested prompts

Use these exact prompts to walk through the conversation:

- `Hi`
- `How are you?`
- `What's the weather like?`
- `Tell me a joke`
- `What can you do?`
- `Goodbye`

## Run locally

From the repository root:

```bash
npm run build -w @microsoft/fast-chat-app-example
npm run start -w @microsoft/fast-chat-app-example
```

Or from this folder:

```bash
npm run build
npm start
```

The build first regenerates `index.html` from `entry.html`, `state.json`, and `templates.html`, then runs TypeScript and Vite.

## Files

- `entry.html` β€” source document for the FAST build step
- `state.json` β€” initial state used while pre-rendering the page shell
- `templates.html` β€” declarative FAST templates for the custom elements
- `build-markup.mjs` β€” runs `@microsoft/fast-build` and injects the templates into `index.html`
- `public/styles.css` β€” token-based shared stylesheet for the page shell and component shadow roots
- `src/` β€” runtime classes, canned conversation data, and hydration bootstrap
- `index.html` β€” generated pre-rendered page used by Vite

See [DESIGN.md](./DESIGN.md) for architecture details, design-system notes, and the canned conversation data.
10 changes: 10 additions & 0 deletions examples/ssr/chat-app/biome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.8/schema.json",
"root": false,
"extends": "//",
"html": {
"formatter": {
"enabled": false
}
}
}
36 changes: 36 additions & 0 deletions examples/ssr/chat-app/build-markup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";

const require = createRequire(import.meta.url);
const cwd = path.dirname(fileURLToPath(import.meta.url));
const fastCli = require.resolve("@microsoft/fast-build/bin/fast.js");
const configPath = path.join(cwd, "fast-build.config.json");
const templatesPath = path.join(cwd, "templates.html");
const indexPath = path.join(cwd, "index.html");
const scriptTag = '<script type="module" src="./src/main.ts"></script>';

execFileSync(process.execPath, [fastCli, "build", `--config=${configPath}`], {
cwd,
stdio: "inherit",
});

const templates = readFileSync(templatesPath, "utf8").trim();
const rendered = readFileSync(indexPath, "utf8");

if (!rendered.includes(scriptTag)) {
throw new Error("Unable to find the module script while injecting f-templates.");
}

writeFileSync(
indexPath,
rendered.replace(
scriptTag,
`${templates}

${scriptTag}`,
),
"utf8",
);
16 changes: 16 additions & 0 deletions examples/ssr/chat-app/entry.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<title>FAST Chat App</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<chat-app
welcome-title="{{welcomeTitle}}"
composer-placeholder="{{composerPlaceholder}}"
initial-suggestion="{{initialSuggestion}}"
></chat-app>
<script type="module" src="./src/main.ts"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions examples/ssr/chat-app/fast-build.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"entry": "entry.html",
"state": "state.json",
"output": "index.html",
"templates": "templates.html"
}
Loading
Loading