@gjsify/storybook 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -82,6 +82,17 @@ await runStorybook({
82
82
  });
83
83
  ```
84
84
 
85
+ ## Debug it over MCP
86
+
87
+ Run the storybook with `GJSIFY_DEVTOOLS=1` and it exposes the [`@gjsify/devtools`](../devtools) control plane, so an AI agent can drive it:
88
+
89
+ ```bash
90
+ GJSIFY_DEVTOOLS=1 gjsify storybook # exposes org.gjsify.Devtools
91
+ gjsify debug --profile storybook # MCP bridge: list_stories / get_current_story / open_story / set_story_arg (+ generic screenshot)
92
+ ```
93
+
94
+ See the [Debugging & remote control guide](https://gjsify.github.io/gjsify/guides/devtools/).
95
+
85
96
  ## Exports
86
97
 
87
98
  - `StoryWidget` — Adw.Bin base class; `fromMeta()`, `addContent()`, `initialize()`/`updateArgs()`/`teardown()` hooks
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{StoryRegistryService as e}from"./registry.js";import{STORYBOOK_CSS as t}from"./styles.js";import{StorybookWindow as n}from"./window.js";import r from"@girs/adw-1";import i from"@girs/gdk-4.0";import a from"@girs/gio-2.0";import o from"@girs/gobject-2.0";import s from"@girs/gtk-4.0";var c=class StorybookApplication extends r.Application{static{o.registerClass({GTypeName:`StorybookApplication`},StorybookApplication)}constructor(t){super({application_id:t.applicationId,flags:a.ApplicationFlags.DEFAULT_FLAGS}),this._window=null,this._registry=new e,this._options=t,this.connect(`startup`,()=>this._initStyles()),this.connect(`activate`,()=>this._onActivate())}_initStyles(){let e=new s.CssProvider;e.load_from_string(`${t}\n${this._options.css??``}`);let n=i.Display.get_default();if(!n){console.error(`No display found`);return}s.StyleContext.add_provider_for_display(n,e,s.STYLE_PROVIDER_PRIORITY_APPLICATION)}_onActivate(){if(!this._window){this._window=new n({application:this}),this._options.title&&this._window.set_title(this._options.title),this._registry.registerStories(this._options.stories);try{let e=this._registry.createStoryInstances();this._window.populateSidebar(e)}catch(e){console.error(`Failed to create story instances:`,e)}}this._window.present()}};o.type_ensure(c.$gtype);export{c as StorybookApplication};
1
+ import"./_virtual/_rolldown/runtime.js";import{storybookDevtoolsExtension as e}from"./devtools-extension.js";import{STORYBOOK_CSS as t}from"./styles.js";import{StorybookWindow as n}from"./window.js";import r from"@girs/adw-1";import i from"@girs/gdk-4.0";import a from"@girs/gio-2.0";import o from"@girs/gobject-2.0";import s from"@girs/gtk-4.0";import{installDevtools as c}from"@gjsify/devtools";var l=class StorybookApplication extends r.Application{static{o.registerClass({GTypeName:`StorybookApplication`},StorybookApplication)}constructor(e){super({application_id:e.applicationId,flags:a.ApplicationFlags.DEFAULT_FLAGS}),this._window=null,this._options=e,this.connect(`startup`,()=>this._initStyles()),this.connect(`activate`,()=>this._onActivate())}_initStyles(){let e=new s.CssProvider;e.load_from_string(`${t}\n${this._options.css??``}`);let n=i.Display.get_default();if(!n){console.error(`No display found`);return}s.StyleContext.add_provider_for_display(n,e,s.STYLE_PROVIDER_PRIORITY_APPLICATION)}_onActivate(){if(!this._window){this._window=new n({application:this}),this._options.title&&this._window.set_title(this._options.title);try{this._window.populateSidebar(this._options.stories)}catch(e){console.error(`Failed to create story instances:`,e)}c(this,{enabled:this._options.devtools||void 0,extend:[e(this._window)]})}this._window.present()}};o.type_ensure(l.$gtype);export{l as StorybookApplication};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{buildStorybookDevtoolsExtension as e}from"@gjsify/storybook-core";const t={ListStories:`<method name="ListStories"><arg type="s" direction="out" name="stories_json"/></method>`,GetCurrentStory:`<method name="GetCurrentStory"><arg type="s" direction="out" name="story_json"/></method>`,OpenStory:`<method name="OpenStory"><arg type="s" direction="in" name="title"/><arg type="b" direction="out" name="opened"/></method>`,SetStoryArg:`<method name="SetStoryArg"><arg type="s" direction="in" name="name"/><arg type="s" direction="in" name="value_json"/><arg type="b" direction="out" name="applied"/></method>`,GetStoryArgs:`<method name="GetStoryArgs"><arg type="s" direction="out" name="args_json"/></method>`};function storybookDevtoolsExtension(n){let r=e(n.controller),i=[],a={},o={};for(let e of r.methods){let n=t[e.name];n&&i.push(n),a[e.name]=e.handler,o[e.name]=e.kind}return{methodsXml:i,handlers:a,methodKinds:o,contributeStatus:r.contributeStatus}}export{storybookDevtoolsExtension};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{isStoryModule as e}from"@gjsify/stories";function collectStoryModules(t){let n=[];for(let r of t)if(!(!r||typeof r!=`object`))for(let t of Object.values(r))e(t)&&n.push(t);return n}export{collectStoryModules};
1
+ import"./_virtual/_rolldown/runtime.js";import{collectStoryModules as e}from"@gjsify/storybook-core";function collectStoryModules(t){return e(t)}export{collectStoryModules};
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import{StoryRegistryService as e}from"./registry.js";import{StorybookWindow as t}from"./window.js";import{StorybookApplication as n}from"./application.js";import{installActionGroup as r,withActionGroup as i}from"./decorators.js";import{collectStoryModules as a}from"./discover.js";import{StoryWidget as o}from"./story-widget.js";import{runStorybook as s}from"./run.js";import{ControlType as c,argsFromControls as l,isStoryModule as u}from"@gjsify/stories";export{c as ControlType,e as StoryRegistryService,o as StoryWidget,n as StorybookApplication,t as StorybookWindow,l as argsFromControls,a as collectStoryModules,r as installActionGroup,u as isStoryModule,s as runStorybook,i as withActionGroup};
1
+ import{storybookDevtoolsExtension as e}from"./devtools-extension.js";import{StorybookWindow as t}from"./window.js";import{StorybookApplication as n}from"./application.js";import{installActionGroup as r,withActionGroup as i}from"./decorators.js";import{collectStoryModules as a}from"./discover.js";import{StoryWidget as o}from"./story-widget.js";import{StoryRegistryService as s}from"./registry.js";import{runStorybook as c}from"./run.js";import{ControlType as l,argsFromControls as u,isStoryModule as d}from"@gjsify/stories";export{l as ControlType,s as StoryRegistryService,o as StoryWidget,n as StorybookApplication,t as StorybookWindow,u as argsFromControls,a as collectStoryModules,r as installActionGroup,d as isStoryModule,c as runStorybook,e as storybookDevtoolsExtension,i as withActionGroup};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";var StoryRegistryService=class{constructor(){this.modules=[]}registerStory(e){if(!e||!Array.isArray(e.stories)){console.warn(`Invalid story module provided to registerStory`);return}this.modules.push(e)}registerStories(e){if(!Array.isArray(e)){console.warn(`Invalid story modules array provided to registerStories`);return}let t=e.filter(e=>e&&Array.isArray(e.stories));this.modules.push(...t)}getStories(){return[...this.modules]}createStoryInstances(){for(let e of this.modules)this.instantiateStoriesForModule(e);return[...this.modules]}instantiateStoriesForModule(e){e.instances||=[];let t=e.decorators??[];for(let n of e.stories)try{let r=new n,build=()=>{typeof r.initialize==`function`&&r.initialize()};t.reduceRight((e,t)=>()=>t(e,r),build)(),e.instances.push(r)}catch(e){console.error(`Failed to instantiate story widget: ${n.name||`Unknown`}`,e)}}};export{StoryRegistryService};
1
+ import"./_virtual/_rolldown/runtime.js";import{StoryRegistry as e,StoryRegistry as t}from"@gjsify/storybook-core";var StoryRegistryService=class extends t{};export{e as StoryRegistry,StoryRegistryService};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gobject-2.0";import n from"@girs/gtk-4.0";import{argsFromControls as r}from"@gjsify/stories";var i=class StoryWidget extends e.Bin{static{t.registerClass({GTypeName:`StoryWidget`,Properties:{meta:t.ParamSpec.object(`meta`,`Meta`,`Story metadata`,t.ParamFlags.READWRITE,t.Object),story:t.ParamSpec.string(`story`,`Story Name`,`Story name`,t.ParamFlags.READWRITE,``),args:t.ParamSpec.object(`args`,`Args`,`Story arguments`,t.ParamFlags.READWRITE,t.Object)}},StoryWidget)}constructor(e,t={}){super(t),this._story=``,this._storyContent=null,this._group=null,this._meta=e.meta,this._story=e.story,this._args=e.args,this.get_child()??this._installDefaultChrome()}static fromMeta(e,t){return{meta:e,story:t,args:r(e.controls)}}get meta(){return this._meta}set meta(e){this._meta!==e&&(this._meta=e,this.notify(`meta`),this._refreshChromeText())}get story(){return this._story}set story(e){this._story!==e&&(this._story=e,this.notify(`story`),this._refreshChromeText())}get args(){return this._args}set args(e){this._args!==e&&(this._args=e,this.notify(`args`),this.updateArgs(e))}initialize(){}updateArgs(e){}teardown(){}addContent(e){if(!this._storyContent)return;let t=this._storyContent.get_first_child();for(;t;)t.unparent(),t=this._storyContent.get_first_child();this._storyContent.append(e)}_installDefaultChrome(){let t=new e.PreferencesPage,r=new e.PreferencesGroup,i=new n.Box({orientation:n.Orientation.VERTICAL,halign:n.Align.CENTER,valign:n.Align.CENTER,margin_top:12,margin_bottom:12,spacing:12,hexpand:!0});r.add(i),t.add(r),this.set_child(t),this._group=r,this._storyContent=i,this._refreshChromeText()}_refreshChromeText(){if(!this._group)return;let e=this._meta?this._story?`${this._meta.title} — ${this._story}`:this._meta.title:this._story,t=this._meta?.description??``;this._group.set_title(e),this._group.set_description(t)}};t.type_ensure(i.$gtype);export{i as StoryWidget};
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gobject-2.0";import n from"@girs/gtk-4.0";import{StoryViewBase as r}from"@gjsify/storybook-core";import{argsFromControls as i}from"@gjsify/stories";function buildGtkChrome(t){let r=new e.PreferencesPage,i=new e.PreferencesGroup,a=new n.Box({orientation:n.Orientation.VERTICAL,halign:n.Align.CENTER,valign:n.Align.CENTER,margin_top:12,margin_bottom:12,spacing:12,hexpand:!0});return a.add_css_class(`story-stage`),i.add(a),r.add(i),t.set_child(r),{root:t,hasStage:!0,setStageContent(e){let t=a.get_first_child();for(;t;)t.unparent(),t=a.get_first_child();a.append(e)},setChromeText(e,t){i.set_title(e),i.set_description(t)}}}var StoryCore=class extends r{constructor(e,t,n){super(),this._widget=e,this.initBase(t,n)}createChrome(e){return buildGtkChrome(this._widget)}initialize(){this._widget.initialize()}updateArgs(e){this._widget.updateArgs(e)}teardown(){this._widget.teardown()}emitArgs(e){super.emitArgs(e),this._widget.notify(`args`)}},a=class StoryWidget extends e.Bin{static{t.registerClass({GTypeName:`StoryWidget`,Properties:{meta:t.ParamSpec.object(`meta`,`Meta`,`Story metadata`,t.ParamFlags.READWRITE,t.Object),story:t.ParamSpec.string(`story`,`Story Name`,`Story name`,t.ParamFlags.READWRITE,``),args:t.ParamSpec.object(`args`,`Args`,`Story arguments`,t.ParamFlags.READWRITE,t.Object)}},StoryWidget)}constructor(e,t={}){super(t),this._core=new StoryCore(this,e.meta,e.story)}static fromMeta(e,t){return{meta:e,story:t,args:i(e.controls)}}get meta(){return this._core.meta}get story(){return this._core.story}get args(){return this._core.args}set args(e){this._core.args=e}onArgsChanged(e){return this._core.onArgsChanged(e)}setArg(e,t){this._core.setArg(e,t)}initialize(){}updateArgs(e){}teardown(){}addContent(e){this._core.addContent(e)}};t.type_ensure(a.$gtype);export{a as StoryWidget};
package/lib/esm/styles.js CHANGED
@@ -7,4 +7,22 @@ const e=`
7
7
  .story-range-row > box {
8
8
  border-radius: 12px;
9
9
  }
10
+
11
+ /* Controls panel shares the window (story) background; only the left
12
+ navigation sidebar keeps a distinct shade. The overlay-split-view sidebar is
13
+ otherwise drawn on the sidebar shade — flatten it onto the window colour. */
14
+ .storybook-controls > .sidebar,
15
+ .storybook-controls > .sidebar > * {
16
+ background-color: @window_bg_color;
17
+ }
18
+
19
+ /* Subtle dashed frame around the live preview so the widget's bounds stay
20
+ locatable even when it is transparent or in an empty state (e.g. a collapsed
21
+ bottom sheet, a status page, a bare button). @window_fg_color flips with the
22
+ theme (alpha(currentColor, …) does not resolve in a Gtk.CssProvider). */
23
+ .story-stage {
24
+ border: 1px dashed alpha(@window_fg_color, 0.28);
25
+ border-radius: 12px;
26
+ padding: 18px;
27
+ }
10
28
  `;export{e as STORYBOOK_CSS};
package/lib/esm/window.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gdk-4.0";import n from"@girs/gobject-2.0";import r from"@girs/gtk-4.0";import{ControlType as i}from"@gjsify/stories";var a=class StorybookWindow extends e.ApplicationWindow{static{n.registerClass({GTypeName:`StorybookWindow`},StorybookWindow)}constructor(e){super(e),this._controlRows=[],this._controlRefreshers=[],this._activeStoryHandlerId=0,this._activeStory=null,this.set_default_size(1200,800),this.set_size_request(360,320),this.set_title(`Storybook`),this._buildUI(),this._sidebar_list.connect(`row-selected`,this._onStorySelected.bind(this)),this._show_controls_button.connect(`toggled`,this._onToggleControls.bind(this)),this._controls_split_view.set_show_sidebar(!0)}_buildUI(){this._sidebar_list=new r.ListBox({selection_mode:r.SelectionMode.SINGLE}),this._sidebar_list.add_css_class(`navigation-sidebar`);let t=new r.ScrolledWindow({hexpand:!0,vexpand:!0,child:this._sidebar_list}),n=new e.HeaderBar({title_widget:new e.WindowTitle({title:`Stories`}),show_end_title_buttons:!1}),i=new e.ToolbarView({content:t});i.add_top_bar(n);let a=new e.NavigationPage({title:`Stories`,tag:`stories`,child:i});this._content_area=new e.Bin({hexpand:!0,vexpand:!0});let o=new r.ScrolledWindow({hexpand:!0,vexpand:!0,child:this._content_area});this._control_panel=new e.PreferencesGroup({title:`Controls`});let s=new e.PreferencesPage;s.add(this._control_panel);let c=new r.ScrolledWindow({vexpand:!0,child:s});this._controls_split_view=new e.OverlaySplitView({show_sidebar:!0,sidebar_position:r.PackType.END,min_sidebar_width:280,max_sidebar_width:360,content:o,sidebar:c}),this._preview_title=new e.WindowTitle({title:`Preview`}),this._show_controls_button=new r.ToggleButton({icon_name:`sidebar-show-right-symbolic`,tooltip_text:`Toggle Controls`,active:!0});let l=new e.HeaderBar({title_widget:this._preview_title});l.pack_end(this._show_controls_button);let u=new e.ToolbarView({content:this._controls_split_view});u.add_top_bar(l);let d=new e.NavigationPage({title:`Preview`,tag:`preview`,child:u});this._main_split_view=new e.NavigationSplitView({min_sidebar_width:220,max_sidebar_width:320,sidebar:a,content:d}),this.set_content(this._main_split_view);let f=e.BreakpointCondition.parse(`max-width: 720sp`);if(f){let t=new e.Breakpoint({condition:f});t.connect(`apply`,()=>{this._main_split_view.collapsed=!0,this._controls_split_view.collapsed=!0}),t.connect(`unapply`,()=>{this._main_split_view.collapsed=!1,this._controls_split_view.collapsed=!1}),this.add_breakpoint(t)}}_onToggleControls(e){this._controls_split_view.set_show_sidebar(e.get_active())}populateSidebar(e){if(this._clearSidebar(),!e.some(e=>e.instances?.length)){console.error(`Story modules do not have instances. Call createStoryInstances first.`);return}this._groupStoriesByCategory(e).forEach((e,t)=>{this._addCategoryToSidebar(t),e.forEach(e=>{this._addStoryToSidebar(e)})})}_clearSidebar(){let e=this._sidebar_list.get_first_child();for(;e;){let t=e.get_next_sibling();e.unparent(),e=t}}_groupStoriesByCategory(e){let t=new Map;return e.forEach(e=>{e.instances?.length&&e.instances.forEach(e=>{let[n]=e.meta.title.split(`/`);t.has(n)||t.set(n,[]),t.get(n).push(e)})}),t}_addCategoryToSidebar(e){let t=new r.ListBoxRow({selectable:!1}),n=new r.Label({label:e,halign:r.Align.START,margin_top:10,margin_bottom:4,margin_start:12,margin_end:12});n.add_css_class(`heading`),n.add_css_class(`dim-label`),t.set_child(n),this._sidebar_list.append(t)}_addStoryToSidebar(e){let t=e.meta.title.split(`/`),n=t.length>1?t[1]:e.meta.title,i=new r.ListBoxRow,a=new r.Label({label:n||`Unnamed Story`,halign:r.Align.START,margin_start:20,margin_top:6,margin_bottom:6});i.set_child(a),this._sidebar_list.append(i),i.storyWidget=e}_onStorySelected(e,t){if(!t)return;let n=t;n.storyWidget&&(this._showStory(n.storyWidget),this._main_split_view.get_collapsed()&&this._main_split_view.set_show_content(!0))}_showStory(e){this._preview_title.set_title(`${e.meta.title} - ${e.story}`),this._content_area.set_child(e),this._updateControlPanel(e)}_updateControlPanel(e){this._clearControlPanel();let t=e.meta.controls;Array.isArray(t)&&(t.forEach(t=>{if(!t?.name||!t?.type){console.warn(`Invalid control configuration:`,t);return}let n=this._createControlRow(e,t);n&&(this._control_panel.add(n),this._controlRows.push(n))}),this._activeStory&&this._activeStoryHandlerId&&this._activeStory.disconnect(this._activeStoryHandlerId),this._activeStory=e,this._activeStoryHandlerId=e.connect(`notify::args`,()=>{for(let t of this._controlRefreshers)t(e.args)}),this._show_controls_button.set_active(!0))}_clearControlPanel(){for(let e of this._controlRows)this._control_panel.remove(e);this._controlRows=[],this._controlRefreshers=[]}_createControlRow(e,t){let n=e.args[t.name];switch(t.type){case i.TEXT:return this._createTextRow(e,t,typeof n==`string`?n:``);case i.NUMBER:return this._createNumberRow(e,t,typeof n==`number`?n:t.min??0);case i.BOOLEAN:return this._createBooleanRow(e,t,typeof n==`boolean`?n:!1);case i.RANGE:return this._createRangeRow(e,t,typeof n==`number`?n:t.min??0);case i.SELECT:return this._createSelectRow(e,t,n??null);case i.COLOR:return this._createColorRow(e,t,typeof n==`string`?n:`#000000`);default:return console.warn(`Unsupported control type: ${t.type}`),null}}_writeArg(e,t,n){e.args={...e.args,[t]:n}}_createTextRow(t,n,r){let i=new e.EntryRow({title:n.label||n.name});return i.set_text(r),n.description&&i.set_tooltip_text(n.description),i.connect(`changed`,()=>this._writeArg(t,n.name,i.get_text())),this._controlRefreshers.push(e=>{let t=typeof e[n.name]==`string`?e[n.name]:``;i.get_text()!==t&&i.set_text(t)}),i}_createNumberRow(t,n,r){let i=e.SpinRow.new_with_range(n.min??0,n.max??100,n.step??1);return i.set_title(n.label||n.name),n.description&&i.set_subtitle(n.description),i.set_value(r),i.connect(`changed`,()=>this._writeArg(t,n.name,i.get_value())),this._controlRefreshers.push(e=>{let t=typeof e[n.name]==`number`?e[n.name]:0;i.get_value()!==t&&i.set_value(t)}),i}_createBooleanRow(t,n,r){let i=new e.SwitchRow({title:n.label||n.name,subtitle:n.description??``,active:r});return i.connect(`notify::active`,()=>this._writeArg(t,n.name,i.get_active())),this._controlRefreshers.push(e=>{let t=!!e[n.name];i.get_active()!==t&&i.set_active(t)}),i}_createRangeRow(e,t,n){let i=t.min??0,a=t.max??100,o=t.step??1,s=Number.isInteger(o)&&Number.isInteger(n),c=new r.Adjustment({lower:i,upper:a,step_increment:o,value:n});c.step_increment=o;let l=new r.ListBoxRow({selectable:!1,activatable:!1});l.add_css_class(`story-range-row`);let u=new r.Box({orientation:r.Orientation.VERTICAL,spacing:6,margin_top:12,margin_bottom:12,margin_start:12,margin_end:12}),d=new r.Box({orientation:r.Orientation.HORIZONTAL,spacing:12}),f=new r.Label({label:t.label||t.name,halign:r.Align.START,hexpand:!0,ellipsize:3});f.add_css_class(`heading`);let p=new r.Label({label:this._formatRangeValue(n,s),halign:r.Align.END});if(p.add_css_class(`numeric`),p.add_css_class(`dim-label`),d.append(f),d.append(p),u.append(d),t.description){let e=new r.Label({label:t.description,halign:r.Align.START,xalign:0,wrap:!0,max_width_chars:32});e.add_css_class(`caption`),e.add_css_class(`dim-label`),u.append(e)}let m=new r.Scale({orientation:r.Orientation.HORIZONTAL,adjustment:c,draw_value:!1,hexpand:!0});return m.connect(`value-changed`,()=>{let n=m.get_value();if(s){let e=Math.round(n);if(e!==n){m.set_value(e);return}n=e}p.set_label(this._formatRangeValue(n,s)),this._writeArg(e,t.name,n)}),u.append(m),l.set_child(u),this._controlRefreshers.push(e=>{let r=typeof e[t.name]==`number`?e[t.name]:n;m.get_value()!==r&&(m.set_value(r),p.set_label(this._formatRangeValue(r,s)))}),l}_formatRangeValue(e,t){return t?String(Math.round(e)):e.toFixed(2)}_createSelectRow(t,n,i){let a=n.options;if(!a?.length)return console.warn(`SELECT control "${n.name}" has no options`),null;let o=r.StringList.new(a.map(e=>e.label)),s=new e.ComboRow({title:n.label||n.name,subtitle:n.description??``,model:o}),c=a.findIndex(e=>e.value===i);return c>=0&&s.set_selected(c),s.connect(`notify::selected`,()=>{let e=s.get_selected();e>=0&&e<a.length&&this._writeArg(t,n.name,a[e].value)}),this._controlRefreshers.push(e=>{let t=e[n.name],r=a.findIndex(e=>e.value===t);r>=0&&s.get_selected()!==r&&s.set_selected(r)}),s}_createColorRow(n,i,a){let o=new e.ActionRow({title:i.label||i.name,subtitle:i.description??``}),s=new r.ColorDialogButton({dialog:new r.ColorDialog({title:i.label||i.name}),valign:r.Align.CENTER}),c=new t.RGBA;return c.parse(a)&&s.set_rgba(c),s.connect(`notify::rgba`,()=>{this._writeArg(n,i.name,this._rgbaToHex(s.get_rgba()))}),o.add_suffix(s),o.set_activatable_widget(s),o}_rgbaToHex(e){let channel=e=>Math.round(Math.max(0,Math.min(1,e))*255).toString(16).padStart(2,`0`);return`#${channel(e.red)}${channel(e.green)}${channel(e.blue)}`}};n.type_ensure(a.$gtype);export{a as StorybookWindow};
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/adw-1";import t from"@girs/gdk-4.0";import n from"@girs/gobject-2.0";import r from"@girs/gtk-4.0";import{StorybookController as i,bindControl as a}from"@gjsify/storybook-core";function adwControlWidgetFactory(){return{text(t,n){let r=new e.EntryRow({title:t});n&&r.set_tooltip_text(n);let i=null;return r.connect(`changed`,()=>i?.(r.get_text())),{node:r,get:()=>r.get_text(),set:e=>r.set_text(e),onChange:e=>{i=e}}},boolean(t,n){let r=new e.SwitchRow({title:t,subtitle:n??``}),i=null;return r.connect(`notify::active`,()=>i?.(r.get_active())),{node:r,get:()=>r.get_active(),set:e=>r.set_active(e),onChange:e=>{i=e}}},number(t){let n=e.SpinRow.new_with_range(t.min??0,t.max??100,t.step??1);n.set_title(t.label||t.name),t.description&&n.set_subtitle(t.description);let r=null;return n.connect(`changed`,()=>r?.(n.get_value())),{node:n,get:()=>n.get_value(),set:e=>n.set_value(e),onChange:e=>{r=e}}},range(e){return buildRangeWidget(e)},select(e){return buildSelectWidget(e)},color(e,t){return buildColorWidget(e,t)}}}function buildRangeWidget(e){let t=e.min??0,n=e.max??100,i=e.step??1,a=typeof e.min==`number`?e.min:0,o=Number.isInteger(i)&&Number.isInteger(a),s=new r.Adjustment({lower:t,upper:n,step_increment:i,value:a});s.step_increment=i;let c=new r.ListBoxRow({selectable:!1,activatable:!1});c.add_css_class(`story-range-row`);let l=new r.Box({orientation:r.Orientation.VERTICAL,spacing:6,margin_top:12,margin_bottom:12,margin_start:12,margin_end:12}),u=new r.Box({orientation:r.Orientation.HORIZONTAL,spacing:12}),d=new r.Label({label:e.label||e.name,halign:r.Align.START,hexpand:!0,ellipsize:3});d.add_css_class(`heading`);let formatValue=e=>o?String(Math.round(e)):e.toFixed(2),f=new r.Label({label:formatValue(a),halign:r.Align.END});if(f.add_css_class(`numeric`),f.add_css_class(`dim-label`),u.append(d),u.append(f),l.append(u),e.description){let t=new r.Label({label:e.description,halign:r.Align.START,xalign:0,wrap:!0,max_width_chars:32});t.add_css_class(`caption`),t.add_css_class(`dim-label`),l.append(t)}let p=new r.Scale({orientation:r.Orientation.HORIZONTAL,adjustment:s,draw_value:!1,hexpand:!0}),m=null,h=!1;return p.connect(`value-changed`,()=>{if(h)return;let e=p.get_value();if(o){let t=Math.round(e);if(t!==e){p.set_value(t);return}e=t}f.set_label(formatValue(e)),m?.(e)}),l.append(p),c.set_child(l),{node:c,get:()=>p.get_value(),set:e=>{h=!0,p.set_value(e),h=!1,f.set_label(formatValue(e))},onChange:e=>{m=e}}}function buildSelectWidget(t){let n=t.options??[],i=r.StringList.new(n.map(e=>e.label)),a=new e.ComboRow({title:t.label||t.name,subtitle:t.description??``,model:i}),o=null;return a.connect(`notify::selected`,()=>o?.(a.get_selected())),{node:a,get:()=>a.get_selected(),set:e=>a.set_selected(e),onChange:e=>{o=e}}}function buildColorWidget(n,i){let a=new e.ActionRow({title:n,subtitle:i??``}),o=new r.ColorDialogButton({dialog:new r.ColorDialog({title:n}),valign:r.Align.CENTER});a.add_suffix(o),a.set_activatable_widget(o);let channel=e=>Math.round(Math.max(0,Math.min(1,e))*255).toString(16).padStart(2,`0`),rgbaToHex=e=>`#${channel(e.red)}${channel(e.green)}${channel(e.blue)}`,s=null,c=!1;return o.connect(`notify::rgba`,()=>{c||s?.(rgbaToHex(o.get_rgba()))}),{node:a,get:()=>rgbaToHex(o.get_rgba()),set:e=>{let n=new t.RGBA;n.parse(e)&&(c=!0,o.set_rgba(n),c=!1)},onChange:e=>{s=e}}}var o=class StorybookWindow extends e.ApplicationWindow{static{n.registerClass({GTypeName:`StorybookWindow`},StorybookWindow)}constructor(e){super(e),this._controlRows=[],this._rowByTitle=new Map,this._onSelect=null,this._suppressSelect=!1,this.set_default_size(1200,800),this.set_size_request(360,320),this.set_title(`Storybook`),this._buildUI(),this._controller=new i(this,e=>this._buildControls(e)),this._sidebar_list.connect(`row-selected`,this._onRowSelected.bind(this)),this._show_controls_button.connect(`toggled`,this._onToggleControls.bind(this)),this._controls_split_view.set_show_sidebar(!0)}_onRowSelected(e,t){if(this._suppressSelect||!t)return;let n=t;n.storyWidget&&this._onSelect?.(n.storyWidget)}get controller(){return this._controller}_buildControls(e){let t=e.meta.controls;if(!Array.isArray(t))return[];let n=adwControlWidgetFactory(),r=[];for(let i of t){if(!i?.name||!i?.type){console.warn(`Invalid control configuration:`,i);continue}let t=a(e,i,n);t&&r.push(t)}return r}_buildUI(){this._sidebar_list=new r.ListBox({selection_mode:r.SelectionMode.SINGLE}),this._sidebar_list.add_css_class(`navigation-sidebar`);let t=new r.ScrolledWindow({hexpand:!0,vexpand:!0,child:this._sidebar_list}),n=new e.HeaderBar({title_widget:new e.WindowTitle({title:`Stories`}),show_end_title_buttons:!1}),i=new e.ToolbarView({content:t,top_bar_style:e.ToolbarStyle.FLAT});i.add_top_bar(n);let a=new e.NavigationPage({title:`Stories`,tag:`stories`,child:i});this._content_area=new e.Bin({hexpand:!0,vexpand:!0});let o=new r.ScrolledWindow({hexpand:!0,vexpand:!0,child:this._content_area});this._control_panel=new e.PreferencesGroup({title:`Controls`});let s=new e.PreferencesPage;s.add(this._control_panel);let c=new r.ScrolledWindow({vexpand:!0,child:s});this._controls_split_view=new e.OverlaySplitView({show_sidebar:!0,sidebar_position:r.PackType.END,min_sidebar_width:280,max_sidebar_width:360,content:o,sidebar:c,css_classes:[`storybook-controls`]}),this._preview_title=new e.WindowTitle({title:`Preview`}),this._show_controls_button=new r.ToggleButton({icon_name:`sidebar-show-right-symbolic`,tooltip_text:`Toggle Controls`,active:!0});let l=new e.HeaderBar({title_widget:this._preview_title});l.pack_end(this._show_controls_button);let u=new e.ToolbarView({content:this._controls_split_view,top_bar_style:e.ToolbarStyle.FLAT});u.add_top_bar(l);let d=new e.NavigationPage({title:`Preview`,tag:`preview`,child:u});this._main_split_view=new e.NavigationSplitView({min_sidebar_width:220,max_sidebar_width:320,sidebar:a,content:d}),this.set_content(this._main_split_view);let f=e.BreakpointCondition.parse(`max-width: 720sp`);if(f){let t=new e.Breakpoint({condition:f});t.connect(`apply`,()=>{this._main_split_view.collapsed=!0,this._controls_split_view.collapsed=!0}),t.connect(`unapply`,()=>{this._main_split_view.collapsed=!1,this._controls_split_view.collapsed=!1}),this.add_breakpoint(t)}}_onToggleControls(e){this._controls_split_view.set_show_sidebar(e.get_active())}populateSidebar(e){this._controller.mount(e,!0)}renderSidebar(e,t){this._onSelect=t,this._suppressSelect=!0,this._clearSidebar(),this._rowByTitle.clear();for(let t of e){this._addCategoryToSidebar(t.category);for(let{instance:e,name:n}of t.stories)this._addStoryToSidebar(e,n)}this._suppressSelect=!1}markSelected(e){let t=this._rowByTitle.get(e);t&&this._sidebar_list.get_selected_row()!==t&&(this._suppressSelect=!0,this._sidebar_list.select_row(t),this._suppressSelect=!1)}setPreviewTitle(e){this._preview_title.set_title(e)}showPreview(e){this._content_area.set_child(e),this._main_split_view.get_collapsed()&&this._main_split_view.set_show_content(!0)}renderControls(e){this._clearControlPanel();for(let t of e){let e=t.view;this._control_panel.add(e),this._controlRows.push(e)}this._show_controls_button.set_active(!0)}_clearSidebar(){let e=this._sidebar_list.get_first_child();for(;e;){let t=e.get_next_sibling();e.unparent(),e=t}}_addCategoryToSidebar(e){let t=new r.ListBoxRow({selectable:!1}),n=new r.Label({label:e,halign:r.Align.START,margin_top:10,margin_bottom:4,margin_start:12,margin_end:12});n.add_css_class(`heading`),n.add_css_class(`dim-label`),t.set_child(n),this._sidebar_list.append(t)}_addStoryToSidebar(e,t){let n=new r.ListBoxRow,i=new r.Label({label:t||`Unnamed Story`,halign:r.Align.START,margin_start:20,margin_top:6,margin_bottom:6});n.set_child(i),this._sidebar_list.append(n),n.storyWidget=e,this._rowByTitle.set(e.meta.title,n)}_clearControlPanel(){for(let e of this._controlRows)this._control_panel.remove(e);this._controlRows=[]}get activeStory(){return this._controller.activeStory}openStoryByTitle(e){return this._controller.openStoryByTitle(e)}setActiveArg(e,t){return this._controller.setActiveArg(e,t)}};n.type_ensure(o.$gtype);export{o as StorybookWindow};
@@ -10,6 +10,8 @@ export interface StorybookOptions {
10
10
  stories: StoryModule[];
11
11
  /** Consumer's own widget stylesheet, layered on top of the built-in chrome CSS. */
12
12
  css?: string;
13
+ /** Force-enable the devtools control plane (otherwise gated on `GJSIFY_DEVTOOLS`). */
14
+ devtools?: boolean;
13
15
  }
14
16
  /**
15
17
  * Hosts the storybook: loads styles on startup, builds the window and populates
@@ -18,7 +20,6 @@ export interface StorybookOptions {
18
20
  */
19
21
  export declare class StorybookApplication extends Adw.Application {
20
22
  private _window;
21
- private _registry;
22
23
  private _options;
23
24
  constructor(options: StorybookOptions);
24
25
  private _initStyles;
@@ -0,0 +1,11 @@
1
+ import type { DevtoolsExtension } from '@gjsify/devtools';
2
+ import type { StorybookWindow } from './window.js';
3
+ /**
4
+ * Build a {@link DevtoolsExtension} that exposes the storybook's story surface
5
+ * — list/open stories, read/set the active story's args — so an agent (via the
6
+ * MCP bridge) can drive and debug widgets in isolation. Wired by
7
+ * {@link StorybookApplication} when `GJSIFY_DEVTOOLS` is set.
8
+ *
9
+ * Delegates the method set to the window's {@link StorybookController}.
10
+ */
11
+ export declare function storybookDevtoolsExtension(window: StorybookWindow): DevtoolsExtension;
@@ -1,8 +1,8 @@
1
- import type { StoryModule as GtkStoryModule } from './story-widget.js';
1
+ import type { StoryModule } from './story-widget.js';
2
2
  /**
3
3
  * Flatten a list of module namespaces (the `import * as m from './x.story.js'`
4
- * objects the `gjsify storybook` command generates) into the story modules
4
+ * objects the `gjsify storybook` command generates) into the GTK story modules
5
5
  * they export — regardless of the export name. Any exported value shaped like a
6
- * {@link StoryModule} (an object with a `stories` array) is collected.
6
+ * story module (an object with a `stories` array) is collected.
7
7
  */
8
- export declare function collectStoryModules(namespaces: Array<Record<string, unknown>>): GtkStoryModule[];
8
+ export declare function collectStoryModules(namespaces: Array<Record<string, unknown>>): StoryModule[];
@@ -4,6 +4,7 @@ export type { StoryDecorator, StoryModule, StoryWidgetConstructor } from './stor
4
4
  export { StoryRegistryService } from './registry.js';
5
5
  export { installActionGroup, withActionGroup, type StoryAction } from './decorators.js';
6
6
  export { StorybookWindow } from './window.js';
7
+ export { storybookDevtoolsExtension } from './devtools-extension.js';
7
8
  export { StorybookApplication, type StorybookOptions } from './application.js';
8
9
  export { runStorybook } from './run.js';
9
10
  export { collectStoryModules } from './discover.js';
@@ -1,22 +1,11 @@
1
- import type { StoryModule } from './story-widget.js';
1
+ import { StoryRegistry } from '@gjsify/storybook-core';
2
+ import type { StoryWidget } from './story-widget.js';
3
+ export { StoryRegistry } from '@gjsify/storybook-core';
2
4
  /**
3
- * Manages story-module registration and instantiation. Unlike the original
4
- * PixelRPG implementation there is no exported singleton — each
5
- * {@link runStorybook} run owns exactly one registry, which removes the
6
- * double-registration footgun.
5
+ * Manages story-module registration and instantiation for the GTK renderer.
6
+ * One registry per {@link runStorybook} run (no shared singleton). All behaviour
7
+ * — validation, defensive copies, decorator `reduceRight` composition — is the
8
+ * core {@link StoryRegistry}.
7
9
  */
8
- export declare class StoryRegistryService {
9
- private modules;
10
- /** Register a single story module. */
11
- registerStory(storyModule: StoryModule): void;
12
- /** Register multiple story modules (invalid ones are filtered out). */
13
- registerStories(storyModules: StoryModule[]): void;
14
- /** All registered story modules (a shallow copy). */
15
- getStories(): StoryModule[];
16
- /**
17
- * Instantiate every registered story widget and prepare it for display.
18
- * Call once GTK is ready.
19
- */
20
- createStoryInstances(): StoryModule[];
21
- private instantiateStoriesForModule;
10
+ export declare class StoryRegistryService extends StoryRegistry<StoryWidget> {
22
11
  }
@@ -1,7 +1,7 @@
1
1
  import Adw from '@girs/adw-1';
2
2
  import GObject from '@girs/gobject-2.0';
3
3
  import Gtk from '@girs/gtk-4.0';
4
- import { type StoryArgs, type StoryMeta } from '@gjsify/stories';
4
+ import { type StoryArgs, type StoryArgValue, type StoryMeta } from '@gjsify/stories';
5
5
  export declare namespace StoryWidget {
6
6
  interface ConstructorProps {
7
7
  meta: StoryMeta;
@@ -10,7 +10,8 @@ export declare namespace StoryWidget {
10
10
  }
11
11
  }
12
12
  /**
13
- * Base class for GJS story widgets.
13
+ * Base class for GJS story widgets — a thin GTK adapter over
14
+ * {@link StoryViewBase}.
14
15
  *
15
16
  * Provides default chrome — a centered `Adw.PreferencesPage` /
16
17
  * `Adw.PreferencesGroup` with title + description above a content slot — built
@@ -19,11 +20,7 @@ export declare namespace StoryWidget {
19
20
  * providing their own composite template (then {@link addContent} is a no-op).
20
21
  */
21
22
  export declare class StoryWidget extends Adw.Bin {
22
- private _meta;
23
- private _story;
24
- private _args;
25
- private _storyContent;
26
- private _group;
23
+ private _core;
27
24
  constructor(params: StoryWidget.ConstructorProps, adwParams?: Partial<Adw.Bin.ConstructorProps>);
28
25
  /**
29
26
  * Build the constructor params from a story's metadata, deriving the initial
@@ -31,11 +28,13 @@ export declare class StoryWidget extends Adw.Bin {
31
28
  */
32
29
  static fromMeta(meta: StoryMeta, story: string): StoryWidget.ConstructorProps;
33
30
  get meta(): StoryMeta;
34
- set meta(value: StoryMeta);
35
31
  get story(): string;
36
- set story(value: string);
37
32
  get args(): StoryArgs;
38
33
  set args(value: StoryArgs);
34
+ /** Subscribe to args changes; returns an unsubscribe function. */
35
+ onArgsChanged(listener: (args: StoryArgs) => void): () => void;
36
+ /** Set a single arg — drives {@link updateArgs} and notifies listeners. */
37
+ setArg(name: string, value: StoryArgValue): void;
39
38
  /** Override in subclasses — build the preview. Runs in the registry init path. */
40
39
  initialize(): void;
41
40
  /** Override in subclasses — react to a control change. */
@@ -47,8 +46,6 @@ export declare class StoryWidget extends Adw.Bin {
47
46
  * installed its own composite template.
48
47
  */
49
48
  addContent(widget: Gtk.Widget): void;
50
- private _installDefaultChrome;
51
- private _refreshChromeText;
52
49
  }
53
50
  /** Wraps a story's content-build step (e.g. to install a shared action group). */
54
51
  export type StoryDecorator = (build: () => void, widget: StoryWidget) => void;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -1 +1 @@
1
- export declare const STORYBOOK_CSS = "\n/* Range-control card in the controls sidebar \u2014 keeps the label + description\n from being squashed into a single column when the sidebar is narrow. */\n.story-range-row {\n padding: 0;\n}\n.story-range-row > box {\n border-radius: 12px;\n}\n";
1
+ export declare const STORYBOOK_CSS = "\n/* Range-control card in the controls sidebar \u2014 keeps the label + description\n from being squashed into a single column when the sidebar is narrow. */\n.story-range-row {\n padding: 0;\n}\n.story-range-row > box {\n border-radius: 12px;\n}\n\n/* Controls panel shares the window (story) background; only the left\n navigation sidebar keeps a distinct shade. The overlay-split-view sidebar is\n otherwise drawn on the sidebar shade \u2014 flatten it onto the window colour. */\n.storybook-controls > .sidebar,\n.storybook-controls > .sidebar > * {\n background-color: @window_bg_color;\n}\n\n/* Subtle dashed frame around the live preview so the widget's bounds stay\n locatable even when it is transparent or in an empty state (e.g. a collapsed\n bottom sheet, a status page, a bare button). @window_fg_color flips with the\n theme (alpha(currentColor, \u2026) does not resolve in a Gtk.CssProvider). */\n.story-stage {\n border: 1px dashed alpha(@window_fg_color, 0.28);\n border-radius: 12px;\n padding: 18px;\n}\n";
@@ -1,12 +1,15 @@
1
1
  import Adw from '@girs/adw-1';
2
- import type { StoryModule } from './story-widget.js';
2
+ import type { StoryArgValue } from '@gjsify/stories';
3
+ import { type CategoryGroup, type ControlRow, StorybookController, type StorybookView } from '@gjsify/storybook-core';
4
+ import type { StoryModule, StoryWidget } from './story-widget.js';
3
5
  /**
4
6
  * Main window for the storybook. The sidebar lists story instances grouped by
5
7
  * the `Category/Name` title prefix; the preview pane holds the active
6
8
  * {@link StoryWidget} and an `Adw.PreferencesGroup` of controls drives its
7
- * `args` with two-way binding.
9
+ * `args` with two-way binding. Implements {@link StorybookView} so the shared
10
+ * {@link StorybookController} drives it.
8
11
  */
9
- export declare class StorybookWindow extends Adw.ApplicationWindow {
12
+ export declare class StorybookWindow extends Adw.ApplicationWindow implements StorybookView<StoryWidget> {
10
13
  private _sidebar_list;
11
14
  private _content_area;
12
15
  private _control_panel;
@@ -15,30 +18,44 @@ export declare class StorybookWindow extends Adw.ApplicationWindow {
15
18
  private _controls_split_view;
16
19
  private _main_split_view;
17
20
  private _controlRows;
18
- private _controlRefreshers;
19
- private _activeStoryHandlerId;
20
- private _activeStory;
21
+ private _rowByTitle;
22
+ private _controller;
23
+ private _onSelect;
24
+ private _suppressSelect;
21
25
  constructor(params: Partial<Adw.ApplicationWindow.ConstructorProps>);
26
+ private _onRowSelected;
27
+ /** The controller driving this window (exposed for the devtools extension). */
28
+ get controller(): StorybookController<StoryWidget>;
29
+ private _buildControls;
22
30
  private _buildUI;
23
31
  private _onToggleControls;
24
- /** Populate the sidebar with story instances grouped by category. */
32
+ /**
33
+ * Populate the sidebar with story modules. Registers + instantiates them via
34
+ * the controller (which renders the sidebar through this view's
35
+ * {@link renderSidebar} seam) and opens the first story.
36
+ */
25
37
  populateSidebar(storyModules: StoryModule[]): void;
38
+ renderSidebar(groups: Array<CategoryGroup<StoryWidget>>, onSelect: (instance: StoryWidget) => void): void;
39
+ markSelected(title: string): void;
40
+ setPreviewTitle(title: string): void;
41
+ showPreview(instance: StoryWidget): void;
42
+ renderControls(rows: Array<ControlRow<unknown>>): void;
26
43
  private _clearSidebar;
27
- private _groupStoriesByCategory;
28
44
  private _addCategoryToSidebar;
29
45
  private _addStoryToSidebar;
30
- private _onStorySelected;
31
- private _showStory;
32
- private _updateControlPanel;
33
46
  private _clearControlPanel;
34
- private _createControlRow;
35
- private _writeArg;
36
- private _createTextRow;
37
- private _createNumberRow;
38
- private _createBooleanRow;
39
- private _createRangeRow;
40
- private _formatRangeValue;
41
- private _createSelectRow;
42
- private _createColorRow;
43
- private _rgbaToHex;
47
+ /** The currently-displayed story, or null. */
48
+ get activeStory(): StoryWidget | null;
49
+ /**
50
+ * Select + show a story by its full `Category/Name` title; the sidebar
51
+ * selection updates too, so the UI stays in sync. Returns false when no
52
+ * story with that title exists.
53
+ */
54
+ openStoryByTitle(title: string): boolean;
55
+ /**
56
+ * Set one arg on the active story — drives the same `args` path as the
57
+ * control panel, so the visible control rows refresh via the controller's
58
+ * args subscription. Returns false when no story is active.
59
+ */
60
+ setActiveArg(name: string, value: StoryArgValue): boolean;
44
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/storybook",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Reusable GTK/Adwaita storybook for GJS — author *.story.ts and browse them in a generic component explorer",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -41,11 +41,13 @@
41
41
  "@girs/glib-2.0": "2.88.0-4.0.4",
42
42
  "@girs/gobject-2.0": "2.88.0-4.0.4",
43
43
  "@girs/gtk-4.0": "4.23.0-4.0.4",
44
- "@gjsify/stories": "^0.10.0"
44
+ "@gjsify/devtools": "^0.12.0",
45
+ "@gjsify/stories": "^0.12.0",
46
+ "@gjsify/storybook-core": "^0.12.0"
45
47
  },
46
48
  "devDependencies": {
47
- "@gjsify/cli": "^0.10.0",
48
- "@gjsify/unit": "^0.10.0",
49
+ "@gjsify/cli": "^0.12.0",
50
+ "@gjsify/unit": "^0.12.0",
49
51
  "@types/node": "^25.9.2",
50
52
  "typescript": "^6.0.3"
51
53
  },