From efa8d0886909241ee8a146530125022808d9e4b6 Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Thu, 14 May 2026 14:25:30 -0700 Subject: [PATCH 1/9] feat: add chat app example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- examples/chat-app/DESIGN.md | 144 ++++++ examples/chat-app/README.md | 51 ++ examples/chat-app/build-markup.mjs | 36 ++ examples/chat-app/entry.html | 20 + examples/chat-app/fast-build.config.json | 6 + examples/chat-app/index.html | 586 +++++++++++++++++++++++ examples/chat-app/package.json | 34 ++ examples/chat-app/src/chat-app.ts | 202 ++++++++ examples/chat-app/src/chat-card.ts | 16 + examples/chat-app/src/chat-data.ts | 86 ++++ examples/chat-app/src/chat-message.ts | 16 + examples/chat-app/src/chat-suggestion.ts | 20 + examples/chat-app/src/exports.ts | 5 + examples/chat-app/src/main.ts | 13 + examples/chat-app/state.json | 5 + examples/chat-app/templates.html | 314 ++++++++++++ examples/chat-app/tsconfig.json | 31 ++ examples/chat-app/vite.config.ts | 13 + package-lock.json | 56 ++- 19 files changed, 1632 insertions(+), 22 deletions(-) create mode 100644 examples/chat-app/DESIGN.md create mode 100644 examples/chat-app/README.md create mode 100644 examples/chat-app/build-markup.mjs create mode 100644 examples/chat-app/entry.html create mode 100644 examples/chat-app/fast-build.config.json create mode 100644 examples/chat-app/index.html create mode 100644 examples/chat-app/package.json create mode 100644 examples/chat-app/src/chat-app.ts create mode 100644 examples/chat-app/src/chat-card.ts create mode 100644 examples/chat-app/src/chat-data.ts create mode 100644 examples/chat-app/src/chat-message.ts create mode 100644 examples/chat-app/src/chat-suggestion.ts create mode 100644 examples/chat-app/src/exports.ts create mode 100644 examples/chat-app/src/main.ts create mode 100644 examples/chat-app/state.json create mode 100644 examples/chat-app/templates.html create mode 100644 examples/chat-app/tsconfig.json create mode 100644 examples/chat-app/vite.config.ts diff --git a/examples/chat-app/DESIGN.md b/examples/chat-app/DESIGN.md new file mode 100644 index 00000000000..c3fb2895bdf --- /dev/null +++ b/examples/chat-app/DESIGN.md @@ -0,0 +1,144 @@ +# Chat App Example Design + +## Overview + +`examples/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. + +## Folder structure + +This example follows the same top-level shape as `examples/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`. +- `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. +- `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/main.ts` — 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` calls `enableHydration()` first, 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. +- The example keeps all component styling inline inside `templates.html` to avoid extra stylesheet extraction steps and to keep the FAST build pipeline easy to inspect. +- Dynamic messages are appended at runtime with DOM APIs (and streamed bot HTML) while the overall shell and component authoring remain declarative FAST. diff --git a/examples/chat-app/README.md b/examples/chat-app/README.md new file mode 100644 index 00000000000..aab1fc279bb --- /dev/null +++ b/examples/chat-app/README.md @@ -0,0 +1,51 @@ +# 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()`. + +## What it demonstrates + +- declarative FAST custom elements via `declarativeTemplate()` +- 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` +- `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 and the canned conversation data. diff --git a/examples/chat-app/build-markup.mjs b/examples/chat-app/build-markup.mjs new file mode 100644 index 00000000000..f7a936ed13d --- /dev/null +++ b/examples/chat-app/build-markup.mjs @@ -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 = ''; + +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", +); diff --git a/examples/chat-app/entry.html b/examples/chat-app/entry.html new file mode 100644 index 00000000000..1f76407f92c --- /dev/null +++ b/examples/chat-app/entry.html @@ -0,0 +1,20 @@ + + + + + FAST Chat App + + + + + + + diff --git a/examples/chat-app/fast-build.config.json b/examples/chat-app/fast-build.config.json new file mode 100644 index 00000000000..c291972cdad --- /dev/null +++ b/examples/chat-app/fast-build.config.json @@ -0,0 +1,6 @@ +{ + "entry": "entry.html", + "state": "state.json", + "output": "index.html", + "templates": "templates.html" +} diff --git a/examples/chat-app/index.html b/examples/chat-app/index.html new file mode 100644 index 00000000000..6a959c34f02 --- /dev/null +++ b/examples/chat-app/index.html @@ -0,0 +1,586 @@ + + + + + FAST Chat App + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/chat-app/package.json b/examples/chat-app/package.json new file mode 100644 index 00000000000..26e72b6c39d --- /dev/null +++ b/examples/chat-app/package.json @@ -0,0 +1,34 @@ +{ + "name": "@microsoft/fast-chat-app-example", + "version": "1.0.0", + "description": "Declarative FAST chat app example with iframe streaming", + "main": "dist/exports.js", + "type": "module", + "private": true, + "scripts": { + "build:markup": "node ./build-markup.mjs", + "build": "npm run build:markup && tsgo -p tsconfig.json && vite build", + "start": "npm run build:markup && vite", + "test": "npm run build" + }, + "author": { + "name": "Microsoft", + "url": "https://discord.gg/FcSNfg4" + }, + "homepage": "https://www.fast.design/", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/Microsoft/fast.git", + "directory": "examples/chat-app" + }, + "dependencies": { + "@microsoft/fast-element": "^3.0.0-rc.1", + "tslib": "^2.6.3" + }, + "devDependencies": { + "@genesiscommunitysuccess/cep-fast-plugin": "5.0.3", + "@genesiscommunitysuccess/custom-elements-lsp": "5.0.3", + "@microsoft/fast-build": "*" + } +} diff --git a/examples/chat-app/src/chat-app.ts b/examples/chat-app/src/chat-app.ts new file mode 100644 index 00000000000..a211672bbc8 --- /dev/null +++ b/examples/chat-app/src/chat-app.ts @@ -0,0 +1,202 @@ +import type { FASTElementTemplateResolver } from "@microsoft/fast-element"; +import { attr } from "@microsoft/fast-element/attr.js"; +import { declarativeTemplate } from "@microsoft/fast-element/declarative.js"; +import { FASTElement } from "@microsoft/fast-element/fast-element.js"; +import type { ChatTurn } from "./chat-data.js"; +import { cannedTurnMap, fallbackTurn } from "./chat-data.js"; + +function escapeAttribute(value: string): string { + return value + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); +} + +function renderSuggestion(text: string): string { + return ``; +} + +export class ChatApp extends FASTElement { + @attr({ attribute: "welcome-title" }) + public welcomeTitle: string = "FAST Chat Demo"; + + @attr({ attribute: "composer-placeholder" }) + public composerPlaceholder: string = "Type one of the suggested prompts exactly."; + + @attr({ attribute: "initial-suggestion" }) + public initialSuggestion: string = "Hi"; + + public composer: HTMLTextAreaElement | null = null; + public transcript: HTMLElement | null = null; + + private readonly _queue: ChatTurn[] = []; + private _isStreaming = false; + + public handleSubmit(event: Event): void { + event.preventDefault(); + this.sendCurrentMessage(); + } + + public handleComposerKeydown(event: KeyboardEvent): void { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + this.sendCurrentMessage(); + } + } + + public handleSuggestion(event: Event): void { + const suggestionEvent = event as CustomEvent; + const suggestion = suggestionEvent.detail; + + if (!this.composer || typeof suggestion !== "string") { + return; + } + + this.composer.value = suggestion; + this.composer.focus(); + this.composer.setSelectionRange(suggestion.length, suggestion.length); + } + + private sendCurrentMessage(): void { + if (!this.composer || !this.transcript) { + return; + } + + const message = this.composer.value.trim(); + + if (!message) { + return; + } + + this.appendUserMessage(message); + this.composer.value = ""; + this.scrollTranscript(); + + this._queue.push(cannedTurnMap.get(message) ?? fallbackTurn); + void this.processQueue(); + } + + private appendUserMessage(message: string): void { + if (!this.transcript) { + return; + } + + const messageElement = document.createElement("chat-message"); + const paragraph = document.createElement("p"); + + messageElement.setAttribute("kind", "user"); + paragraph.textContent = message; + messageElement.append(paragraph); + this.transcript.append(messageElement); + } + + private async processQueue(): Promise { + if (this._isStreaming) { + return; + } + + const nextTurn = this._queue.shift(); + + if (!nextTurn) { + return; + } + + this._isStreaming = true; + + try { + await this.streamTurn(nextTurn); + } finally { + this._isStreaming = false; + } + + if (this._queue.length > 0) { + void this.processQueue(); + } + } + + private async streamTurn(turn: ChatTurn): Promise { + await this.streamIntoTranscript([ + ...turn.responseChunks, + renderSuggestion(turn.nextSuggestedUserMessage), + ]); + } + + private streamIntoTranscript(chunks: readonly string[]): Promise { + return new Promise((resolve, reject) => { + const iframe = document.createElement("iframe"); + + iframe.setAttribute("aria-hidden", "true"); + iframe.tabIndex = -1; + iframe.style.display = "none"; + document.body.append(iframe); + + iframe.onload = () => { + iframe.onload = null; + + const iframeDocument = iframe.contentDocument; + + if (!iframeDocument || !this.transcript) { + iframe.remove(); + reject(new Error("Streaming iframe failed to initialize.")); + return; + } + + iframeDocument.write(""); + + const streamingElement = + iframeDocument.querySelector("streaming-element"); + + if (!streamingElement) { + iframe.remove(); + reject(new Error("Unable to create the streaming element.")); + return; + } + + this.transcript.append(streamingElement); + + const writeChunks = async (): Promise => { + for (const chunk of chunks) { + iframeDocument.write(chunk); + this.scrollTranscript(); + await this.delay(160); + } + + iframeDocument.write(""); + iframeDocument.close(); + this.scrollTranscript(); + iframe.remove(); + resolve(); + }; + + void writeChunks().catch(error => { + iframe.remove(); + reject(error); + }); + }; + + iframe.src = ""; + }); + } + + private scrollTranscript(): void { + if (!this.transcript) { + return; + } + + this.transcript.scrollTop = this.transcript.scrollHeight; + } + + private delay(ms: number): Promise { + return new Promise(resolve => { + window.setTimeout(resolve, ms); + }); + } +} + +export const chatAppDefinition = { + name: "chat-app", + template: declarativeTemplate() as unknown as FASTElementTemplateResolver< + typeof ChatApp + >, +}; diff --git a/examples/chat-app/src/chat-card.ts b/examples/chat-app/src/chat-card.ts new file mode 100644 index 00000000000..99276f478cf --- /dev/null +++ b/examples/chat-app/src/chat-card.ts @@ -0,0 +1,16 @@ +import type { FASTElementTemplateResolver } from "@microsoft/fast-element"; +import { attr } from "@microsoft/fast-element/attr.js"; +import { declarativeTemplate } from "@microsoft/fast-element/declarative.js"; +import { FASTElement } from "@microsoft/fast-element/fast-element.js"; + +export class ChatCard extends FASTElement { + @attr + public heading?: string; +} + +export const chatCardDefinition = { + name: "chat-card", + template: declarativeTemplate() as unknown as FASTElementTemplateResolver< + typeof ChatCard + >, +}; diff --git a/examples/chat-app/src/chat-data.ts b/examples/chat-app/src/chat-data.ts new file mode 100644 index 00000000000..d295ac6ccb8 --- /dev/null +++ b/examples/chat-app/src/chat-data.ts @@ -0,0 +1,86 @@ +export interface ChatTurn { + userMessage: string; + responseHtml: string; + responseChunks: readonly string[]; + nextSuggestedUserMessage: string; +} + +function createTurn( + userMessage: string, + responseChunks: readonly string[], + nextSuggestedUserMessage: string, +): ChatTurn { + return { + userMessage, + responseHtml: responseChunks.join(""), + responseChunks, + nextSuggestedUserMessage, + }; +} + +export const cannedTurns: readonly ChatTurn[] = [ + createTurn( + "Hi", + [ + `

Hi there! I'm doing a short round of friendly small talk.

`, + `

I stream this answer a piece at a time through a hidden iframe.

`, + `
`, + ], + "How are you?", + ), + createTurn( + "How are you?", + [ + `

I'm doing well—steady, predictable, and very chatty today.

`, + `

My replies are canned, but the parser-driven streaming is real.

`, + `
`, + ], + "What's the weather like?", + ), + createTurn( + "What's the weather like?", + [ + `

I don't check real forecasts, but here's a cheerful pretend report:

`, + `
  • clear skies
  • light breeze
  • perfect demo weather
`, + `

Ask for a joke if you want to keep the conversation moving.

`, + ], + "Tell me a joke", + ), + createTurn( + "Tell me a joke", + [ + `

Why did the chat bot bring a ladder?

`, + `

Because the conversation kept going to the next level.

`, + `

I'll admit that one lands best with a generous audience.

`, + ], + "What can you do?", + ), + createTurn( + "What can you do?", + [ + `

I can walk through a tiny scripted chat and show a few interface patterns:

`, + `
  • exact message matching
  • streamed HTML chunks
  • FAST custom elements inside each reply
`, + `

Say goodbye and I'll wrap the demo up.

`, + ], + "Goodbye", + ), + createTurn( + "Goodbye", + [ + `

Goodbye! Thanks for trying the demo.

`, + `

If you want another pass, paste Hi back into the composer.

`, + ], + "Hi", + ), +]; + +export const cannedTurnMap = new Map(cannedTurns.map(turn => [turn.userMessage, turn])); + +export const fallbackTurn = createTurn( + "fallback", + [ + `

I only know a small scripted set of lines in this example.

`, + `

Use the suggested prompt so the exact-match lookup can find a canned reply.

`, + ], + "Hi", +); diff --git a/examples/chat-app/src/chat-message.ts b/examples/chat-app/src/chat-message.ts new file mode 100644 index 00000000000..f0a8c6c45ca --- /dev/null +++ b/examples/chat-app/src/chat-message.ts @@ -0,0 +1,16 @@ +import type { FASTElementTemplateResolver } from "@microsoft/fast-element"; +import { attr } from "@microsoft/fast-element/attr.js"; +import { declarativeTemplate } from "@microsoft/fast-element/declarative.js"; +import { FASTElement } from "@microsoft/fast-element/fast-element.js"; + +export class ChatMessage extends FASTElement { + @attr + public kind?: string; +} + +export const chatMessageDefinition = { + name: "chat-message", + template: declarativeTemplate() as unknown as FASTElementTemplateResolver< + typeof ChatMessage + >, +}; diff --git a/examples/chat-app/src/chat-suggestion.ts b/examples/chat-app/src/chat-suggestion.ts new file mode 100644 index 00000000000..3f1a3214b48 --- /dev/null +++ b/examples/chat-app/src/chat-suggestion.ts @@ -0,0 +1,20 @@ +import type { FASTElementTemplateResolver } from "@microsoft/fast-element"; +import { attr } from "@microsoft/fast-element/attr.js"; +import { declarativeTemplate } from "@microsoft/fast-element/declarative.js"; +import { FASTElement } from "@microsoft/fast-element/fast-element.js"; + +export class ChatSuggestion extends FASTElement { + @attr + public text?: string; + + public emitSuggestion(): void { + this.$emit("use-suggestion", this.text ?? ""); + } +} + +export const chatSuggestionDefinition = { + name: "chat-suggestion", + template: declarativeTemplate() as unknown as FASTElementTemplateResolver< + typeof ChatSuggestion + >, +}; diff --git a/examples/chat-app/src/exports.ts b/examples/chat-app/src/exports.ts new file mode 100644 index 00000000000..7b3af7b6305 --- /dev/null +++ b/examples/chat-app/src/exports.ts @@ -0,0 +1,5 @@ +export * from "./chat-app.js"; +export * from "./chat-card.js"; +export * from "./chat-data.js"; +export * from "./chat-message.js"; +export * from "./chat-suggestion.js"; diff --git a/examples/chat-app/src/main.ts b/examples/chat-app/src/main.ts new file mode 100644 index 00000000000..347bb28dabe --- /dev/null +++ b/examples/chat-app/src/main.ts @@ -0,0 +1,13 @@ +import { FASTElement } from "@microsoft/fast-element/fast-element.js"; +import { enableHydration } from "@microsoft/fast-element/hydration.js"; +import { ChatApp, chatAppDefinition } from "./chat-app.js"; +import { ChatCard, chatCardDefinition } from "./chat-card.js"; +import { ChatMessage, chatMessageDefinition } from "./chat-message.js"; +import { ChatSuggestion, chatSuggestionDefinition } from "./chat-suggestion.js"; + +enableHydration(); + +await FASTElement.define(ChatMessage, chatMessageDefinition); +await FASTElement.define(ChatCard, chatCardDefinition); +await FASTElement.define(ChatSuggestion, chatSuggestionDefinition); +await FASTElement.define(ChatApp, chatAppDefinition); diff --git a/examples/chat-app/state.json b/examples/chat-app/state.json new file mode 100644 index 00000000000..57ea21b2f7d --- /dev/null +++ b/examples/chat-app/state.json @@ -0,0 +1,5 @@ +{ + "welcomeTitle": "FAST Chat Demo", + "composerPlaceholder": "Type one of the suggested prompts exactly.", + "initialSuggestion": "Hi" +} diff --git a/examples/chat-app/templates.html b/examples/chat-app/templates.html new file mode 100644 index 00000000000..de6bcc2007c --- /dev/null +++ b/examples/chat-app/templates.html @@ -0,0 +1,314 @@ + + + + + + + + + + + + + + + diff --git a/examples/chat-app/tsconfig.json b/examples/chat-app/tsconfig.json new file mode 100644 index 00000000000..5729766b3e8 --- /dev/null +++ b/examples/chat-app/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "plugins": [ + { + "name": "@genesiscommunitysuccess/custom-elements-lsp", + "srcRouteFromTSServer": "../../../examples/chat-app", + "parser": { + "fastEnable": true, + "timeout": 2000, + "dependencies": ["!**/@custom-elements-manifest/**/*"] + }, + "plugins": ["@genesiscommunitysuccess/cep-fast-plugin"] + } + ], + "pretty": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "importHelpers": true, + "experimentalDecorators": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "noEmitOnError": true, + "strict": true, + "outDir": "dist", + "rootDir": "src", + "lib": ["dom", "esnext"] + }, + "include": ["src"] +} diff --git a/examples/chat-app/vite.config.ts b/examples/chat-app/vite.config.ts new file mode 100644 index 00000000000..431215bc572 --- /dev/null +++ b/examples/chat-app/vite.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + build: { + outDir: "www", + emptyOutDir: true, + sourcemap: true, + }, + server: { + port: 9000, + open: !process.env.CI, + }, +}); diff --git a/package-lock.json b/package-lock.json index 95b8affe34d..074e07ae3eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,26 @@ "clean": "clean.mjs" } }, + "examples/chat-app": { + "name": "@microsoft/fast-chat-app-example", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@microsoft/fast-element": "^3.0.0-rc.1", + "tslib": "^2.6.3" + }, + "devDependencies": { + "@genesiscommunitysuccess/cep-fast-plugin": "5.0.3", + "@genesiscommunitysuccess/custom-elements-lsp": "5.0.3", + "@microsoft/fast-build": "*" + } + }, + "examples/chat-app/node_modules/@microsoft/fast-element": { + "version": "3.0.0-rc.1", + "resolved": "https://registry.npmjs.org/@microsoft/fast-element/-/fast-element-3.0.0-rc.1.tgz", + "integrity": "sha512-c4gtNzySRViqRQAhJKHLzIWZiWFwYXJGfFW0G5nKE1lovIPGnNhnVwOASDHKkAlL/cAHz/C65rsqYSo+Jbfq4Q==", + "license": "MIT" + }, "examples/csr/todo-app": { "name": "@microsoft/fast-todo-app-example", "version": "1.0.0", @@ -86,8 +106,7 @@ "extraneous": true, "license": "MIT", "dependencies": { - "@microsoft/fast-element": "^2.10.4", - "@microsoft/fast-examples-design-system": "^1.0.0", + "@microsoft/fast-element": "^3.0.0-rc.1", "tslib": "^2.6.3" }, "devDependencies": { @@ -95,17 +114,6 @@ "@genesiscommunitysuccess/custom-elements-lsp": "5.0.3" } }, - "examples/todo-mobx-app": { - "name": "@microsoft/fast-todo-mobx-app-example", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@microsoft/fast-element": "^2.10.4", - "mobx": "^6.13.5", - "tslib": "^2.6.3" - } - }, "node_modules/@11ty/dependency-tree": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.2.tgz", @@ -1566,6 +1574,10 @@ "resolved": "build", "link": true }, + "node_modules/@microsoft/fast-chat-app-example": { + "resolved": "examples/chat-app", + "link": true + }, "node_modules/@microsoft/fast-element": { "resolved": "packages/fast-element", "link": true @@ -2920,9 +2932,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3960,9 +3972,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "funding": [ { "type": "github", @@ -7256,9 +7268,9 @@ } }, "node_modules/ws": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", - "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" From d045fc11aac5e7021d6b2de6d2974474112986ae Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Thu, 14 May 2026 14:34:25 -0700 Subject: [PATCH 2/9] chore: remove unused custom-elements-lsp dev dependencies from chat-app These editor-only TypeScript LSP plugins were inherited from the todo-app template but are not required for chat-app to build or run. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- examples/chat-app/package.json | 2 -- examples/chat-app/tsconfig.json | 12 ------------ package-lock.json | 2 -- 3 files changed, 16 deletions(-) diff --git a/examples/chat-app/package.json b/examples/chat-app/package.json index 26e72b6c39d..83b5d188886 100644 --- a/examples/chat-app/package.json +++ b/examples/chat-app/package.json @@ -27,8 +27,6 @@ "tslib": "^2.6.3" }, "devDependencies": { - "@genesiscommunitysuccess/cep-fast-plugin": "5.0.3", - "@genesiscommunitysuccess/custom-elements-lsp": "5.0.3", "@microsoft/fast-build": "*" } } diff --git a/examples/chat-app/tsconfig.json b/examples/chat-app/tsconfig.json index 5729766b3e8..7ebdc5928a0 100644 --- a/examples/chat-app/tsconfig.json +++ b/examples/chat-app/tsconfig.json @@ -1,17 +1,5 @@ { "compilerOptions": { - "plugins": [ - { - "name": "@genesiscommunitysuccess/custom-elements-lsp", - "srcRouteFromTSServer": "../../../examples/chat-app", - "parser": { - "fastEnable": true, - "timeout": 2000, - "dependencies": ["!**/@custom-elements-manifest/**/*"] - }, - "plugins": ["@genesiscommunitysuccess/cep-fast-plugin"] - } - ], "pretty": true, "target": "ES2022", "module": "ES2022", diff --git a/package-lock.json b/package-lock.json index 074e07ae3eb..00eb5c279c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,8 +59,6 @@ "tslib": "^2.6.3" }, "devDependencies": { - "@genesiscommunitysuccess/cep-fast-plugin": "5.0.3", - "@genesiscommunitysuccess/custom-elements-lsp": "5.0.3", "@microsoft/fast-build": "*" } }, From 0870a3d1348fd96282aa18b150c255f41591661a Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Thu, 14 May 2026 14:53:10 -0700 Subject: [PATCH 3/9] chore: slow streaming chunk delay to 600ms in chat-app Makes the iframe + document.write streaming visibly progressive during manual smoke tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- examples/chat-app/src/chat-app.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/chat-app/src/chat-app.ts b/examples/chat-app/src/chat-app.ts index a211672bbc8..9bb98ed8279 100644 --- a/examples/chat-app/src/chat-app.ts +++ b/examples/chat-app/src/chat-app.ts @@ -159,7 +159,7 @@ export class ChatApp extends FASTElement { for (const chunk of chunks) { iframeDocument.write(chunk); this.scrollTranscript(); - await this.delay(160); + await this.delay(600); } iframeDocument.write("
"); From 8e2b61999e94022349532d3c3825bd8f165d1ea5 Mon Sep 17 00:00:00 2001 From: Jane Chu <7559015+janechu@users.noreply.github.com> Date: Thu, 14 May 2026 15:07:40 -0700 Subject: [PATCH 4/9] remove not defined custom elements style --- examples/chat-app/entry.html | 5 ----- examples/chat-app/index.html | 5 ----- 2 files changed, 10 deletions(-) diff --git a/examples/chat-app/entry.html b/examples/chat-app/entry.html index 1f76407f92c..faf00f4d9d4 100644 --- a/examples/chat-app/entry.html +++ b/examples/chat-app/entry.html @@ -3,11 +3,6 @@ FAST Chat App - FAST Chat App -