Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7be8c33
Add Weather Dashboard V2 planning prompt
amcclain Feb 28, 2026
f6a5ee7
Add Weather Dashboard V2 planning artifacts
amcclain Feb 28, 2026
5a87fa5
Add Weather V2 app scaffolding with wiring infrastructure
amcclain Feb 28, 2026
760d56c
Implement Phase 3 initial widget set with wiring
amcclain Feb 28, 2026
9188172
Add Phase 4 widgets: UnitsToggle, Wind, Markdown, Inspector
amcclain Feb 28, 2026
7e18e8b
Add Phase 5: validation pipeline and JSON spec editor harness
amcclain Feb 28, 2026
12de0c6
Add Phase 6: LLM integration with server proxy and chat harness
amcclain Feb 28, 2026
86f84a7
Update PROGRESS.md with full Phase 1-7 execution log
amcclain Feb 28, 2026
4531f84
Fix runtime bugs found during interactive browser testing
amcclain Feb 28, 2026
e2bcce3
Add resizable right-side harness panel and replace require() with ES …
amcclain Feb 28, 2026
267b58a
Use Hoist markdown component in MarkdownContentWidget
amcclain Feb 28, 2026
f37346f
Convert WeatherDataModel to a HoistService
amcclain Feb 28, 2026
334ef62
Move WeatherWidgetModel to widgets/ and rename to BaseWeatherWidgetModel
amcclain Feb 28, 2026
480f2ac
Fix JSON panel showing stale dashboard state on initial open
amcclain Feb 28, 2026
6dd674b
Improve JSON harness validation UX and remove redundant copy button
amcclain Feb 28, 2026
9866549
Scope validation success toast to JSON harness panel
amcclain Feb 28, 2026
12ebfdb
Merge branch 'develop' into weatherv2
amcclain Feb 28, 2026
3b98ee5
Improve JSON harness UX, persist panel state, and add testIds
amcclain Feb 28, 2026
7b7c919
Move Weather V2 planning docs into example package and add README
amcclain Feb 28, 2026
51858c5
Promote LlmChatService to a proper HoistService, UI polish
amcclain Feb 28, 2026
6cfa528
Chat UX, markdown fix, auto-generated widget titles, and LLM testing
amcclain Feb 28, 2026
8109e5b
Fix widget ID convention mismatch, move auto-titles to DashModel level
amcclain Feb 28, 2026
87e1e57
Dashboard UX overhaul, LLM chat agent, and robust widget title resolu…
amcclain Mar 1, 2026
cf62158
Fix chat panel overflow, add ideal size metadata for input widgets, U…
amcclain Mar 1, 2026
8215fb6
Increase LLM timeout to 120s, add retry/edit on error in chat harness
amcclain Mar 1, 2026
f3f8f5c
Render chat assistant messages as markdown, upgrade default LLM to So…
amcclain Mar 1, 2026
264e058
Fix chat markdown font size to match user messages, add table hover s…
amcclain Mar 1, 2026
542bfd3
Add business-audience guidance to LLM system prompt
amcclain Mar 1, 2026
d4cc546
Add LLM tool use (function calling) for app operations in WeatherV2
amcclain Mar 1, 2026
44c6de9
Merge branch 'develop' into weatherv2
amcclain Mar 1, 2026
9fea272
Fix save_dashboard_as_view tool creating views with empty string group
amcclain Mar 1, 2026
da9ac94
Add widget configuration UI with settings modal, color-coded linkage …
amcclain Mar 1, 2026
fe68479
Add input binding lifecycle management with stale binding culling and…
amcclain Mar 1, 2026
78a22b9
Improve widget settings: markdown codeInput, units enum, and showLege…
amcclain Mar 2, 2026
8563310
Add manual editing toggle with hidePanelHeader widget config
amcclain Mar 2, 2026
0b0c0e3
Add harness panel improvements: equal sizing, response timing, meta p…
amcclain Mar 2, 2026
4f64dd4
Add colored border to input widgets matching their linkage color
amcclain Mar 2, 2026
4389d27
Refine widget visual styling: rounded cards, square indicators, left-…
amcclain Mar 2, 2026
addc813
Show grid background when manual editing is enabled and allow externa…
amcclain Mar 2, 2026
b0c80fb
Add inline ColumnChooser to SummaryGridWidget settings panel
amcclain Mar 2, 2026
c98a668
Merge branch 'develop' into weatherv2
amcclain Mar 3, 2026
c14132e
Move dashboard creation/modification into LLM tool-calling stack
amcclain Mar 3, 2026
77593fd
Merge branch 'develop' into weatherv2
amcclain Mar 4, 2026
53bc955
Merge branch 'develop' into weatherv2
amcclain Mar 5, 2026
a5df2c9
Merge branch 'develop' into weatherv2
amcclain Mar 6, 2026
a56fb2a
Merge branch 'develop' into weatherv2
amcclain Mar 11, 2026
6e3459b
Merge branch 'develop' into weatherv2
amcclain Apr 23, 2026
98c154e
Merge branch 'develop' into weatherv2
amcclain Apr 23, 2026
de71f77
Adopt hoist-react v85 initAsync / installServicesAsync API in weatherv2
amcclain Apr 23, 2026
4f8055d
Merge branch 'develop' into weatherv2
amcclain May 19, 2026
27b1d99
Adopt renamed Runner API in weatherv2 AppModel
amcclain May 20, 2026
45c9231
Merge branch 'develop' into weatherv2
amcclain May 20, 2026
1a77fcd
Merge branch 'develop' into weatherv2
amcclain May 28, 2026
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
6 changes: 6 additions & 0 deletions client-app/src/Bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,20 @@ import {ContactService} from './examples/contact/svc/ContactService';
import {GitHubService} from './core/svc/GitHubService';
import {PortfolioService} from './core/svc/PortfolioService';
import {TaskService} from './examples/todo/TaskService';
import {LlmChatService} from './examples/weatherv2/svc/LlmChatService';
import {LlmToolService} from './examples/weatherv2/svc/LlmToolService';
import {WeatherDataService} from './examples/weatherv2/svc/WeatherDataService';

declare module '@xh/hoist/core' {
// Merge interface with XHApi class to include injected services.
export interface XHApi {
contactService: ContactService;
gitHubService: GitHubService;
llmChatService: LlmChatService;
llmToolService: LlmToolService;
portfolioService: PortfolioService;
taskService: TaskService;
weatherDataService: WeatherDataService;
}

export interface HoistUser {
Expand Down
20 changes: 20 additions & 0 deletions client-app/src/apps/weatherv2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import '../Bootstrap';

import {XH} from '@xh/hoist/core';
import {AppContainer} from '@xh/hoist/desktop/appcontainer';
import {AppComponent} from '../examples/weatherv2/AppComponent';
import {AppModel} from '../examples/weatherv2/AppModel';
import {AuthModel} from '../core/AuthModel';

XH.renderApp({
clientAppCode: 'weatherv2',
clientAppName: 'XH Weather V2',
componentClass: AppComponent,
modelClass: AppModel,
containerClass: AppContainer,
authModelClass: AuthModel,
isMobileApp: false,
enableLogout: true,
showBrowserContextMenu: true,
checkAccess: () => true
});
14 changes: 14 additions & 0 deletions client-app/src/desktop/tabs/examples/ExamplesTabModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,20 @@ export class ExamplesTabModel extends HoistModel {
)
]
},
{
title: 'Weather V2',
icon: Icon.sun(),
path: 'weatherv2',
srcPath: 'weatherv2',
text: [
p(
'Weather Dashboard V2 — a declarative, wirable dashboard with inter-widget bindings and LLM-driven spec generation.'
),
p(
"Demonstrates dashboard-as-DSL: Hoist's native persisted state as a machine-readable spec that an LLM can generate and validate."
)
]
},
{
title: 'Contact',
icon: Icon.users(),
Expand Down
113 changes: 113 additions & 0 deletions client-app/src/examples/weatherv2/AppComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {hoistCmp, uses} from '@xh/hoist/core';
import {frame, hframe} from '@xh/hoist/cmp/layout';
import {appBar, appBarSeparator} from '@xh/hoist/desktop/cmp/appbar';
import {button, dashCanvasAddViewButton} from '@xh/hoist/desktop/cmp/button';
import {panel} from '@xh/hoist/desktop/cmp/panel';
import {dashCanvas, dashCanvasWidgetChooser} from '@xh/hoist/desktop/cmp/dash';
import {viewManager} from '@xh/hoist/desktop/cmp/viewmanager';
import {Icon} from '@xh/hoist/icon';
import {sparklesIcon} from './Icons';
import {AppModel} from './AppModel';
import {jsonHarnessPanel} from './harness/JsonHarnessPanel';
import {chatHarnessPanel} from './harness/ChatHarnessPanel';
import '../../core/Toolbox.scss';
import './WeatherV2.scss';

export const AppComponent = hoistCmp({
displayName: 'App',
model: uses(AppModel),

render({model}) {
const {
weatherViewManager,
weatherV2DashModel,
manualEditingEnabled,
showJsonHarness,
showChatHarness,
showWidgetChooser
} = model,
{dashCanvasModel} = weatherV2DashModel,
activeWidgetChooser = showWidgetChooser && manualEditingEnabled,
showHarness = showChatHarness || showJsonHarness || activeWidgetChooser;

return panel({
tbar: appBar({
icon: Icon.sun({size: '2x', prefix: 'fal'}),
title: 'Weather V2',
rightItems: [
viewManager({model: weatherViewManager}),
appBarSeparator(),
button({
testId: 'manual-editing-btn',
icon: Icon.edit(),
text: 'Manual Editing',
active: manualEditingEnabled,
outlined: true,
intent: manualEditingEnabled ? 'primary' : undefined,
onClick: () => (model.manualEditingEnabled = !manualEditingEnabled)
}),
dashCanvasAddViewButton({
dashCanvasModel,
rightIcon: Icon.chevronDown(),
outlined: true,
disabled: !manualEditingEnabled
}),
appBarSeparator(),
button({
testId: 'chat-btn',
icon: sparklesIcon(),
text: 'Dashboard Agent',
active: showChatHarness,
outlined: true,
intent: showChatHarness ? 'primary' : undefined,
onClick: () => (model.showChatHarness = !showChatHarness)
}),
button({
testId: 'json-btn',
icon: Icon.json(),
text: 'JSON',
active: showJsonHarness,
outlined: true,
intent: showJsonHarness ? 'primary' : undefined,
onClick: () => (model.showJsonHarness = !showJsonHarness)
}),
button({
testId: 'widget-chooser-btn',
icon: Icon.boxFull(),
text: 'Widgets',
active: showWidgetChooser,
outlined: true,
intent: activeWidgetChooser ? 'primary' : undefined,
disabled: !manualEditingEnabled,
onClick: () => (model.showWidgetChooser = !showWidgetChooser)
}),
appBarSeparator()
],
appMenuButtonProps: {hideLogoutItem: false}
}),
item: hframe(
frame(dashCanvas({model: dashCanvasModel})),
panel({
model: model.harnessPanelModel,
items: [
showChatHarness ? chatHarnessPanel() : null,
showJsonHarness ? jsonHarnessPanel() : null,
activeWidgetChooser
? panel({
testId: 'widget-chooser-panel',
title: 'Widget Chooser',
icon: Icon.boxFull(),
compactHeader: true,
flex: 1,
minHeight: 0,
item: dashCanvasWidgetChooser({dashCanvasModel})
})
: null
],
omit: !showHarness
})
),
className: 'weather-v2-app'
});
}
});
72 changes: 72 additions & 0 deletions client-app/src/examples/weatherv2/AppModel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {InitContext, managed, persist, XH} from '@xh/hoist/core';
import {ViewManagerModel} from '@xh/hoist/cmp/viewmanager';
import {PanelModel} from '@xh/hoist/desktop/cmp/panel';
import {bindable, makeObservable} from '@xh/hoist/mobx';
import {
autoRefreshAppOption,
themeAppOption,
sizingModeAppOption
} from '@xh/hoist/desktop/cmp/appOption';
import {BaseAppModel} from '../../BaseAppModel';
import {WeatherV2DashModel} from './dash/WeatherV2DashModel';
import {LlmChatService} from './svc/LlmChatService';
import {LlmToolService} from './svc/LlmToolService';
import {WeatherDataService} from './svc/WeatherDataService';

export class AppModel extends BaseAppModel {
static instance: AppModel;
override persistWith = {localStorageKey: 'weatherV2App'};

@managed weatherV2DashModel: WeatherV2DashModel;
@managed weatherViewManager: ViewManagerModel;
@managed harnessPanelModel: PanelModel;
@persist @bindable manualEditingEnabled: boolean = true;
@persist @bindable showJsonHarness: boolean = false;
@persist @bindable showChatHarness: boolean = true;
@persist @bindable showWidgetChooser: boolean = false;

constructor() {
super();
makeObservable(this);
}

override async initAsync(ctx: InitContext) {
await super.initAsync(ctx);
await XH.installServicesAsync([LlmChatService, LlmToolService, WeatherDataService], ctx);

this.harnessPanelModel = new PanelModel({
side: 'right',
defaultSize: 500,
minSize: 300,
resizable: true,
collapsible: false,
persistWith: {...this.persistWith, path: 'harnessPanel'}
});

await this.newSpan({name: 'toolbox.weatherv2.loadViews', parent: ctx.span}).run(
async () => {
this.weatherViewManager = await ViewManagerModel.createAsync({
type: 'weatherDashboardV2',
typeDisplayName: 'Layout',
enableDefault: true,
enableAutoSave: false,
manageGlobal: XH.getUser().isHoistAdmin
});
}
);

this.weatherV2DashModel = new WeatherV2DashModel(this.weatherViewManager);

this.addReaction({
track: () => this.manualEditingEnabled,
run: editing => {
this.weatherV2DashModel.dashCanvasModel.showGridBackground = editing;
},
fireImmediately: true
});
}

override getAppOptions() {
return [themeAppOption(), sizingModeAppOption(), autoRefreshAppOption()];
}
}
19 changes: 19 additions & 0 deletions client-app/src/examples/weatherv2/Icons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {library} from '@fortawesome/fontawesome-svg-core';
import {
faCloudRain,
faDropletPercent,
faCalendarDays,
faSparkles,
faTemperatureHalf,
faWind
} from '@fortawesome/pro-regular-svg-icons';
import {Icon} from '@xh/hoist/icon';

library.add(faCloudRain, faDropletPercent, faCalendarDays, faSparkles, faTemperatureHalf, faWind);

export const temperatureIcon = (opts = {}) => Icon.icon({iconName: 'temperature-half', ...opts});
export const cloudRainIcon = (opts = {}) => Icon.icon({iconName: 'cloud-rain', ...opts});
export const windIcon = (opts = {}) => Icon.icon({iconName: 'wind', ...opts});
export const dropletPercentIcon = (opts = {}) => Icon.icon({iconName: 'droplet-percent', ...opts});
export const calendarDaysIcon = (opts = {}) => Icon.icon({iconName: 'calendar-days', ...opts});
export const sparklesIcon = (opts = {}) => Icon.icon({iconName: 'sparkles', ...opts});
Loading
Loading