Skip to content
Merged
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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ Status legend: ⬜ Todo · ✅ Done

| # | Component | Variants / notes | Status |
|---|-----------|------------------|--------|
| 24 | Chat Screen | centred 760 rail; zero state (greeting, lifted composer, starters); jump to latest | ✅ |
| 24 | Chat View | centred 760 rail; zero state (greeting, lifted composer, starters); jump to latest | ✅ |
| 25 | SidePanel | | ⬜ |
| 26 | Modal | | ⬜ |
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
retry pill) and a `FlowErrorPart` message part, with
`onRetry`/`errorTitle`/`retryLabel` threaded through `FlowMessage` and
`FlowThread`.
- **Breaking**: `FlowChatScreen` is renamed to `FlowChatView`. The widget
was never a screen — it is body-only and embeddable, and upcoming
surfaces (side panel, modal) will host it — so the name now follows
Flutter's convention for embeddable composites. Rename call sites;
the API is unchanged.
- **Breaking**: a failed assistant turn no longer recolors its content
into an `errorContainer` bubble — parts keep their normal ink and the
turn closes with an error card (a default one when no `FlowErrorPart`
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,6 @@ Values come from the Flow UI Figma file. Role names follow Material 3's `ColorSc

| # | Component | Variants / notes | Status |
|---|-----------|------------------|--------|
| 24 | Chat Screen | centred 760 rail; zero state (greeting, lifted composer, starters); jump to latest | ✅ |
| 24 | Chat View | centred 760 rail; zero state (greeting, lifted composer, starters); jump to latest | ✅ |
| 25 | SidePanel | | ⬜ |
| 26 | Modal | | ⬜ |
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

| Component | What it does |
|---|---|
| [`FlowChatScreen`](https://flowui.stac.dev/components/chat-screen) | The full chat surface: bounded thread over a composer, centred at a readable width, with a zero state (greeting, lifted composer, starters) and a jump-to-latest button |
| [`FlowChatView`](https://flowui.stac.dev/components/chat-view) | The full chat surface: bounded thread over a composer, centred at a readable width, with a zero state (greeting, lifted composer, starters) and a jump-to-latest button |
| [`FlowThread`](https://flowui.stac.dev/components/message-thread) | Scrollable conversation anchored to the newest message |
| [`FlowMessage`](https://flowui.stac.dev/components/message-thread) | One turn — ink-wash user bubble, plain assistant, error bubble, typed content parts |
| [`FlowStreamingText`](https://flowui.stac.dev/components/streaming-text) | Animated text reveal while a reply arrives |
Expand Down Expand Up @@ -136,7 +136,7 @@ class _ChatPageState extends State<ChatPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: FlowChatScreen(
body: FlowChatView(
empty: _messages.isEmpty,
greeting: const FlowGreeting(
icon: Icons.wb_twilight,
Expand Down Expand Up @@ -176,7 +176,7 @@ class _ChatPageState extends State<ChatPage> {
}
```

`FlowChatScreen` is body-only — it builds no `Scaffold` and no app bar, so
`FlowChatView` is body-only — it builds no `Scaffold` and no app bar, so
your app keeps the chrome, the background, and the keyboard inset. See
[`example/lib/main.dart`](example/lib/main.dart) for a complete runnable
version of this page, and the [live playground](https://flowui.stac.dev/playground)
Expand Down
4 changes: 4 additions & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ const playgroundDevIndex = {
// https://astro.build/config
export default defineConfig({
site: 'https://flowui.stac.dev',
// FlowChatScreen became FlowChatView in 0.2.0; keep the old page URL alive.
redirects: {
'/components/chat-screen': '/components/chat-view',
},
integrations: [
playgroundDevIndex,
starlight({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
---
title: Chat screen
title: Chat view
description: The assembled surface — thread above composer on a centred rail, with a zero state and jump-to-latest.
sidebar:
order: 12
---

import FlowDemo from '../../../components/FlowDemo.astro';

`FlowChatScreen` is the chat surface assembled: a bounded thread above a
`FlowChatView` is the chat surface assembled: a bounded thread above a
composer, centred on a readable rail (760 by default), with a zero state
for the conversation that hasn't started and a jump-to-latest button once
you've scrolled back through history.

<FlowDemo demo="full-chat" height={640} title="The chat screen" />
<FlowDemo demo="full-chat" height={640} title="The chat view" />

```dart title="Assemble the surface"
FlowChatScreen(
FlowChatView(
empty: messages.isEmpty,
greeting: FlowGreeting(icon: Icons.wb_twilight, text: 'Good afternoon'),
suggestions: FlowSuggestionGroup(
Expand All @@ -30,7 +30,7 @@ FlowChatScreen(

## It takes finished widgets

The screen accepts a built `FlowThread` and `FlowComposer` rather than
The view accepts a built `FlowThread` and `FlowComposer` rather than
their data, so it stays correct as those components grow. What it adds is
the one thing every host would otherwise have to know: the bounded height
a thread needs, the width caps, and where the zero state's pieces go —
Expand All @@ -47,7 +47,7 @@ transcript.
## Jump to latest

Pass the **same** `ScrollController` to the thread and to
`threadController:` — taking finished widgets means the screen can't
`threadController:` — taking finished widgets means the view can't
reach in and attach its own. Null leaves the button out entirely.

## Key API
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/components/error-state.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ FlowMessageData(

Standalone, the same card serves the other failure surfaces — a thread
that failed to load, a failed send below the composer, or a connection
notice pinned above the input via `FlowChatScreen.aboveComposer`, the
notice pinned above the input via `FlowChatView.aboveComposer`, the
slot that already exists for exactly this:

```dart title="A connection notice above the composer"
FlowChatScreen(
FlowChatView(
thread: FlowThread(messages: messages),
aboveComposer: offline
? FlowErrorState(
Expand Down
4 changes: 2 additions & 2 deletions docs/src/content/docs/components/greeting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import FlowDemo from '../../../components/FlowDemo.astro';
there is room it sets a 40px glyph beside 32px text, bottom-aligned; below
600px of available width it restacks itself — glyph above the text, at the
design's 21px. The host supplies the whole string (name and all);
`FlowChatScreen` centres it when the conversation hasn't started.
`FlowChatView` centres it when the conversation hasn't started.

## Default

Expand Down Expand Up @@ -72,5 +72,5 @@ FlowGreeting(
- `text` — the whole headline; the package composes no copy.
- `icon`, `iconColor` — the optional glyph.
- `textStyle` — overrides the responsive sizing outright.
- In the assembled surface: pass it to `FlowChatScreen`'s `greeting:`
- In the assembled surface: pass it to `FlowChatView`'s `greeting:`
slot, shown only while `empty` is true.
2 changes: 1 addition & 1 deletion docs/src/content/docs/components/message-thread.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ card, a chart) without the library knowing what it is.
## Key classes

- `FlowThread` — the scrolling conversation; give it a `ScrollController`
to pair with `FlowChatScreen`'s jump-to-latest. `padding` (the design's
to pair with `FlowChatView`'s jump-to-latest. `padding` (the design's
16) and `itemSpacing` (32) override the metrics; `messageBuilder`
swaps the default `FlowMessage` per turn; `thinkingLabel`,
`charactersPerSecond`, and `previewCloseTooltip` forward to every
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/components/suggestions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,5 @@ Column(
- `FlowSuggestionColumnScope` — what the column layout wraps its rows in
so a hovered row shows the trailing arrow; public so a host laying out
its own full-width column can opt in.
- `FlowChatScreen` places a column group in its zero state via the
- `FlowChatView` places a column group in its zero state via the
`suggestions:` slot.
4 changes: 2 additions & 2 deletions docs/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ import logo from '../../assets/flow-ui-logo.svg';
<span>Map your Material 3 scheme across, or ship the Flow light and dark presets.</span>
</div>
</a>
<a class="tile" href="/components/chat-screen/">
<a class="tile" href="/components/chat-view/">
<div class="tile__top">
<svg class="tile__icon" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true"><path d="M166,112a6,6,0,0,1-6,6H96a6,6,0,0,1,0-12h64A6,6,0,0,1,166,112Zm-6,26H96a6,6,0,0,0,0,12h64a6,6,0,0,0,0-12Zm70-10A102,102,0,0,1,79.31,217.65L44.44,229.27a14,14,0,0,1-17.71-17.71l11.62-34.87A102,102,0,1,1,230,128Zm-12,0A90,90,0,1,0,50.08,173.06a6,6,0,0,1,.5,4.91L38.12,215.35a2,2,0,0,0,2.53,2.53L78,205.42a6.2,6.2,0,0,1,1.9-.31,6.09,6.09,0,0,1,3,.81A90,90,0,0,0,218,128Z" /></svg>
<span class="tile__num">03</span>
</div>
<div class="tile__body">
<strong>Chat screen</strong>
<strong>Chat view</strong>
<span>Thread, composer, attachments and suggestions in one streaming surface.</span>
</div>
</a>
Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,6 @@ elements and the remaining AI states are on the way.

| Component | Status |
| --- | --- |
| Chat screen | <span class="badge-done">Shipped</span> |
| Chat view | <span class="badge-done">Shipped</span> |
| Side panel | <span class="badge-todo">Planned</span> |
| Modal | <span class="badge-todo">Planned</span> |
2 changes: 1 addition & 1 deletion example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class _ChatPageState extends State<ChatPage> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: context.flowColors.surface,
body: FlowChatScreen(
body: FlowChatView(
empty: _messages.isEmpty,
greeting: const FlowGreeting(
icon: Icons.wb_twilight,
Expand Down
2 changes: 1 addition & 1 deletion lib/flow_ui.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export 'src/theme/flow_theme.dart';
export 'src/theme/flow_typography.dart';
export 'src/widgets/flow_attachment_group.dart';
export 'src/widgets/flow_attachment_preview.dart';
export 'src/widgets/flow_chat_screen.dart';
export 'src/widgets/flow_chat_view.dart';
export 'src/widgets/flow_code_block.dart';
export 'src/widgets/flow_composer.dart';
export 'src/widgets/flow_error_state.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const Duration _jumpScroll = Duration(milliseconds: 240);
/// started.
///
/// ```dart
/// FlowChatScreen(
/// FlowChatView(
/// empty: messages.isEmpty,
/// greeting: FlowGreeting(icon: Icons.wb_twilight, text: 'Good afternoon'),
/// suggestions: FlowSuggestionGroup(...),
Expand All @@ -39,8 +39,8 @@ const Duration _jumpScroll = Duration(milliseconds: 240);
/// Body-only: it builds no [Scaffold] and no app bar, so drop it in a
/// scaffold body and the host keeps the chrome, the background, and the
/// keyboard inset.
class FlowChatScreen extends StatefulWidget {
const FlowChatScreen({
class FlowChatView extends StatefulWidget {
const FlowChatView({
super.key,
this.thread,
this.composer,
Expand Down Expand Up @@ -91,7 +91,7 @@ class FlowChatScreen extends StatefulWidget {
/// centre between them, per the design; on compact ones it stays docked
/// with the suggestions just above it and the greeting floating centred.
///
/// The host flips this (typically `messages.isEmpty`): the screen takes
/// The host flips this (typically `messages.isEmpty`): the view takes
/// finished widgets and cannot see into the thread.
final bool empty;

Expand All @@ -100,7 +100,7 @@ class FlowChatScreen extends StatefulWidget {
final Widget? greeting;

/// The zero state's starters, usually a column `FlowSuggestionGroup`.
/// Shown only while [empty]; the screen places them — 48 below the
/// Shown only while [empty]; the view places them — 48 below the
/// composer on wide layouts, capped at [emptySuggestionsWidth], and 16
/// above it on compact ones, stepped in a further 8.
final Widget? suggestions;
Expand Down Expand Up @@ -134,10 +134,10 @@ class FlowChatScreen extends StatefulWidget {
final EdgeInsetsGeometry? padding;

@override
State<FlowChatScreen> createState() => _FlowChatScreenState();
State<FlowChatView> createState() => _FlowChatViewState();
}

class _FlowChatScreenState extends State<FlowChatScreen> {
class _FlowChatViewState extends State<FlowChatView> {
/// The design's surface metrics. Compact begins below 600, Material's
/// compact/medium boundary — read from this widget's own constraints, so
/// a pane or a phone frame counts, not just a phone. The composer block
Expand Down Expand Up @@ -168,7 +168,7 @@ class _FlowChatScreenState extends State<FlowChatScreen> {
}

@override
void didUpdateWidget(FlowChatScreen oldWidget) {
void didUpdateWidget(FlowChatView oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.threadController != widget.threadController) {
oldWidget.threadController?.removeListener(_handleScroll);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/widgets/flow_error_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import '../theme/flow_theme.dart';
/// becomes this card, and a failed assistant turn closes with a default
/// one even when the host supplies no part. Standalone it serves the
/// other failure surfaces — a thread that failed to load, a connection
/// notice pinned in `FlowChatScreen.aboveComposer`, a failed send below
/// notice pinned in `FlowChatView.aboveComposer`, a failed send below
/// the composer.
///
/// Retry reports intent; what it means — re-run the turn, refetch,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/widgets/flow_greeting.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:material_ui/material_ui.dart';
import '../theme/flow_theme.dart';

/// The zero state's headline — "Good afternoon, …" over a thread nobody has
/// spoken in yet, usually through `FlowChatScreen.greeting`:
/// spoken in yet, usually through `FlowChatView.greeting`:
///
/// ```dart
/// FlowGreeting(
Expand Down
2 changes: 1 addition & 1 deletion lib/src/widgets/flow_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'flow_streaming_text.dart';

/// Renders a [FlowCustomPart]; return null to skip it.
///
/// A full renderer registry waits for the Chat Screen surface — this
/// A full renderer registry waits for the Chat View surface — this
/// callback is the extension seam until then.
typedef FlowCustomPartBuilder =
Widget? Function(
Expand Down
2 changes: 1 addition & 1 deletion lib/src/widgets/flow_thread.dart
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class FlowThread extends StatelessWidget {
final String? thinkingLabel;

/// The design's thread metrics: edge padding and the gap between turns.
/// The edge 16 is mirrored by `FlowChatScreen`'s composer-block padding,
/// The edge 16 is mirrored by `FlowChatView`'s composer-block padding,
/// which promises its edges line up with the thread's.
static const EdgeInsetsGeometry _defaultPadding = EdgeInsets.all(16);
static const double _defaultGap = 32;
Expand Down
2 changes: 1 addition & 1 deletion playground/lib/src/demo_registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ String snippetFor(PlaygroundItem item) {
}

const String _fullChatSnippet = '''
FlowChatScreen(
FlowChatView(
empty: messages.isEmpty,
greeting: const FlowGreeting(
icon: PhosphorIconsRegular.sunHorizon,
Expand Down
4 changes: 2 additions & 2 deletions playground/lib/src/demos/full_chat_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const List<(IconData, String)> _starters = [
(PhosphorIconsRegular.magnifyingGlass, 'Suggest a new venture for me'),
];

/// The Full Chat example: a whole conversation surface on [FlowChatScreen],
/// The Full Chat example: a whole conversation surface on [FlowChatView],
/// live — starts in the zero state, sends for real, streams a canned reply
/// behind the thinking indicator, and carries the add and model menus.
class FullChatDemo extends StatefulWidget {
Expand Down Expand Up @@ -115,7 +115,7 @@ class _FullChatDemoState extends State<FullChatDemo> {

@override
Widget build(BuildContext context) {
return FlowChatScreen(
return FlowChatView(
empty: _messages.isEmpty,
greeting: const FlowGreeting(
icon: PhosphorIconsRegular.sunHorizon,
Expand Down
2 changes: 1 addition & 1 deletion playground/lib/src/demos/thread_demo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:material_ui/material_ui.dart';

const String threadSnippet = '''
// A reversed, scrollable conversation — newest at the bottom. Give it
// bounded height; inside FlowChatScreen that comes for free.
// bounded height; inside FlowChatView that comes for free.
SizedBox(
height: 480,
child: FlowThread(
Expand Down