diff --git a/apps/progress-bar/components/App.tsx b/apps/progress-bar/components/App.tsx
new file mode 100644
index 0000000..1d2d9e9
--- /dev/null
+++ b/apps/progress-bar/components/App.tsx
@@ -0,0 +1,49 @@
+import { Component } from '@geastack/core'
+import { ProgressBar, progressFraction } from '@geastack/elements'
+import { demo } from '../stores/DemoStore'
+
+export class App extends Component {
+ // The buttons drive #demo-bar. The .demo-cases bars below them never change;
+ // they cover default props, a fractional value with an empty label, a
+ // negative range, an inverted range and a NaN value for core's pipeline test.
+ template() {
+ return (
+
+ ProgressBar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+ }
+}
diff --git a/apps/progress-bar/env.d.ts b/apps/progress-bar/env.d.ts
new file mode 100644
index 0000000..dc50b2b
--- /dev/null
+++ b/apps/progress-bar/env.d.ts
@@ -0,0 +1,2 @@
+///
+declare module '*.css'
diff --git a/apps/progress-bar/index.tsx b/apps/progress-bar/index.tsx
new file mode 100644
index 0000000..46c9b1f
--- /dev/null
+++ b/apps/progress-bar/index.tsx
@@ -0,0 +1,5 @@
+import { mount } from '@geastack/core'
+import './styles.css'
+import { App } from './components/App'
+
+mount(App)
diff --git a/apps/progress-bar/package.json b/apps/progress-bar/package.json
new file mode 100644
index 0000000..8aaa3e4
--- /dev/null
+++ b/apps/progress-bar/package.json
@@ -0,0 +1,29 @@
+{
+ "name": "@geastack/example-progress-bar",
+ "type": "module",
+ "scripts": {
+ "check": "tsc --noEmit"
+ },
+ "dependencies": {
+ "@geastack/core": "^0.1.41",
+ "@geastack/elements": "^0.1.6"
+ },
+ "gea": {
+ "id": "progress-bar",
+ "name": "Progress Bar",
+ "entry": "index.tsx",
+ "runtime": "gea",
+ "targets": {
+ "web": true,
+ "esp32": false
+ },
+ "launcher": {
+ "description": "progress bar states",
+ "order": 999,
+ "hidden": true
+ }
+ },
+ "private": true,
+ "license": "MIT",
+ "version": "0.1.0"
+}
diff --git a/apps/progress-bar/stores/DemoStore.ts b/apps/progress-bar/stores/DemoStore.ts
new file mode 100644
index 0000000..4e778ac
--- /dev/null
+++ b/apps/progress-bar/stores/DemoStore.ts
@@ -0,0 +1,61 @@
+import { Store } from '@geastack/core'
+
+// Every button drives one of the cases the ProgressBar has to handle, so the
+// app doubles as the fixture for core's progress-bar pipeline test.
+export class DemoStore extends Store {
+ value = 40
+ min = 0
+ max = 100
+ busy = false
+ still = false
+ wide = true
+
+ // ProgressBar clamps what it draws, not the app's value, so the app keeps its
+ // own value in range. Over and Under bypass this on purpose to exercise the
+ // bar's clamping.
+ step(delta: number) {
+ let next = this.value + delta
+ if (next > this.max) next = this.max
+ if (next < this.min) next = this.min
+ this.value = next
+ }
+
+ setValue(next: number) {
+ this.value = next
+ }
+
+ // min == max leaves the bar nothing to divide by; it must draw empty, not NaN.
+ zeroRange() {
+ this.min = 50
+ this.max = 50
+ }
+
+ reset() {
+ this.value = 40
+ this.min = 0
+ this.max = 100
+ this.busy = false
+ this.still = false
+ this.wide = true
+ }
+
+ toggleBusy() {
+ this.busy = !this.busy
+ }
+
+ toggleStill() {
+ this.still = !this.still
+ }
+
+ toggleWide() {
+ this.wide = !this.wide
+ }
+
+ get label(): string {
+ if (this.busy) return 'Working'
+ const range = 'value ' + this.value + ' of ' + this.min + '-' + this.max
+ return this.max > this.min ? range : range + ' (zero range)'
+ }
+}
+
+export const demo = new DemoStore()
diff --git a/apps/progress-bar/styles.css b/apps/progress-bar/styles.css
new file mode 100644
index 0000000..9c906db
--- /dev/null
+++ b/apps/progress-bar/styles.css
@@ -0,0 +1,51 @@
+body {
+ background-color: #f6f3ea;
+ color: #171713;
+ font-family: 'Inter';
+}
+
+.demo-app {
+ display: flex;
+ flex-direction: column;
+ padding: 24px;
+ gap: 20px;
+}
+
+.demo-title {
+ font-size: 28px;
+ font-weight: 700;
+}
+
+.demo-bar {
+ width: 300px;
+}
+
+.demo-bar--narrow {
+ width: 150px;
+}
+
+.demo-buttons {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.demo-button {
+ width: 70px;
+ height: 44px;
+ background-color: #171713;
+ color: #ffffff;
+ font-size: 14px;
+ border-radius: 8px;
+}
+
+.demo-cases {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.demo-case {
+ width: 300px;
+}
diff --git a/apps/progress-bar/tsconfig.json b/apps/progress-bar/tsconfig.json
new file mode 100644
index 0000000..2388422
--- /dev/null
+++ b/apps/progress-bar/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "lib": [
+ "ES2022"
+ ],
+ "strict": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "jsx": "preserve",
+ "types": []
+ },
+ "include": [
+ "**/*.tsx",
+ "**/*.d.ts",
+ "../../node_modules/@geajs/vite-plugin/gea-env.d.ts"
+ ],
+ "exclude": [
+ "vite.config.ts"
+ ]
+}
diff --git a/docs/EXAMPLE-CATALOG.md b/docs/EXAMPLE-CATALOG.md
index f0ee5d2..cb02832 100644
--- a/docs/EXAMPLE-CATALOG.md
+++ b/docs/EXAMPLE-CATALOG.md
@@ -10,7 +10,7 @@ tests, and marketing proof.
| Watch and wearable UI | `watch`, `watch-analog`, `watch-date`, `watch-face` | Compact layouts, clock/date rendering, small-screen app surfaces. |
| Basic JSX apps | `counter-jsx`, `todo-jsx`, `static-card`, `stopwatch-jsx` | Component rendering, state updates, text/input, timers. |
| Canvas and animation | `bouncing-balls`, `bouncing-balls-jsx`, `canvas-3d`, `canvas-3d-cube`, `css-3d-cube`, `css-animation-showcase` | Canvas drawing, CSS animation, frame loop, transform behavior. |
-| UI components | `settings`, `dialer`, `virtual-list`, `typography`, `bubble-grid`, `bubble-grid-jsx` | Higher-level controls, scroll behavior, text layout, launchable apps. |
+| UI components | `settings`, `dialer`, `virtual-list`, `progress-bar`, `typography`, `bubble-grid`, `bubble-grid-jsx` | Higher-level controls, scroll behavior, text layout, launchable apps. |
| Games | `sky-hop`, `sky-hop-jsx`, `tic-tac-toe`, `tilt-breakout`, `button-tetris` | Interaction, simple game loops, collision/physics, deterministic logic. |
| Device features | `camera-showcase`, `camera-studio`, `voice-notes`, `hid-clicker`, `weather`, `maps` | Target capabilities such as camera, audio, HID, network, and map assets. |
| Apple/native experiments | `notes-jsx`, `notes-native`, `ios-device-showcase`, `ios-metal-*`, `ios-native-showcase`, `macos-native-showcase` | Apple target and native renderer experiments. |
diff --git a/package-lock.json b/package-lock.json
index d71e820..f80f8b4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1378,6 +1378,15 @@
"vite": "latest"
}
},
+ "apps/progress-bar": {
+ "name": "@geastack/example-progress-bar",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "@geastack/core": "^0.1.41",
+ "@geastack/elements": "^0.1.6"
+ }
+ },
"apps/reactive-child-probe": {
"name": "@geastack/example-reactive-child-probe",
"version": "0.1.0",
@@ -2823,6 +2832,10 @@
"resolved": "apps/notes-windows",
"link": true
},
+ "node_modules/@geastack/example-progress-bar": {
+ "resolved": "apps/progress-bar",
+ "link": true
+ },
"node_modules/@geastack/example-reactive-child-probe": {
"resolved": "apps/reactive-child-probe",
"link": true