@gjsify/storybook 0.10.0 → 0.11.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{StoryRegistryService as t}from"./registry.js";import{STORYBOOK_CSS as n}from"./styles.js";import{StorybookWindow as r}from"./window.js";import i from"@girs/adw-1";import a from"@girs/gdk-4.0";import o from"@girs/gio-2.0";import s from"@girs/gobject-2.0";import c from"@girs/gtk-4.0";import{installDevtools as l}from"@gjsify/devtools";var u=class StorybookApplication extends i.Application{static{s.registerClass({GTypeName:`StorybookApplication`},StorybookApplication)}constructor(e){super({application_id:e.applicationId,flags:o.ApplicationFlags.DEFAULT_FLAGS}),this._window=null,this._registry=new t,this._options=e,this.connect(`startup`,()=>this._initStyles()),this.connect(`activate`,()=>this._onActivate())}_initStyles(){let e=new c.CssProvider;e.load_from_string(`${n}\n${this._options.css??``}`);let t=a.Display.get_default();if(!t){console.error(`No display found`);return}c.StyleContext.add_provider_for_display(t,e,c.STYLE_PROVIDER_PRIORITY_APPLICATION)}_onActivate(){if(!this._window){this._window=new r({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)}l(this,{enabled:this._options.devtools||void 0,extend:[e(this._registry,this._window)]})}this._window.present()}};s.type_ensure(u.$gtype);export{u as StorybookApplication};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";function storybookDevtoolsExtension(e,t){let listStories=()=>e.getStories().flatMap(e=>(e.instances??[]).map(e=>({title:e.meta.title,story:e.story,category:e.meta.title.split(`/`)[0],controls:e.meta.controls??[]})));return{methodsXml:[`<method name="ListStories"><arg type="s" direction="out" name="stories_json"/></method>`,`<method name="GetCurrentStory"><arg type="s" direction="out" name="story_json"/></method>`,`<method name="OpenStory"><arg type="s" direction="in" name="title"/><arg type="b" direction="out" name="opened"/></method>`,`<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>`,`<method name="GetStoryArgs"><arg type="s" direction="out" name="args_json"/></method>`],handlers:{ListStories:()=>JSON.stringify(listStories(),null,2),GetCurrentStory:()=>{let e=t.activeStory;return JSON.stringify(e?{title:e.meta.title,story:e.story,args:e.args}:null,null,2)},OpenStory:e=>t.openStoryByTitle(e),SetStoryArg:(e,n)=>t.setActiveArg(e,n?JSON.parse(n):null),GetStoryArgs:()=>JSON.stringify(t.activeStory?.args??{},null,2)},methodKinds:{ListStories:`read-only`,GetCurrentStory:`read-only`,OpenStory:`mutating`,SetStoryArg:`mutating`,GetStoryArgs:`read-only`},contributeStatus:()=>{let e=t.activeStory;return{storybook:{currentStory:e?{title:e.meta.title,story:e.story}:null,storyCount:listStories().length}}}}}export{storybookDevtoolsExtension};
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{StoryRegistryService as t}from"./registry.js";import{StorybookWindow as n}from"./window.js";import{StorybookApplication as r}from"./application.js";import{installActionGroup as i,withActionGroup as a}from"./decorators.js";import{collectStoryModules as o}from"./discover.js";import{StoryWidget as s}from"./story-widget.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,t as StoryRegistryService,s as StoryWidget,r as StorybookApplication,n as StorybookWindow,u as argsFromControls,o as collectStoryModules,i as installActionGroup,d as isStoryModule,c as runStorybook,e as storybookDevtoolsExtension,a as withActionGroup};
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{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}get activeStory(){return this._activeStory}openStoryByTitle(e){let t=this._sidebar_list.get_first_child();for(;t;){let n=t;if(n.storyWidget&&n.storyWidget.meta.title===e)return this._sidebar_list.select_row(n),!0;t=t.get_next_sibling()}return!1}setActiveArg(e,t){let n=this._activeStory;return n?(n.args={...n.args,[e]:t},!0):!1}_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};
@@ -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
@@ -0,0 +1,10 @@
1
+ import type { DevtoolsExtension } from '@gjsify/devtools';
2
+ import type { StoryRegistryService } from './registry.js';
3
+ import type { StorybookWindow } from './window.js';
4
+ /**
5
+ * Build a {@link DevtoolsExtension} that exposes the storybook's story surface
6
+ * — list/open stories, read/set the active story's args — so an agent (via the
7
+ * MCP bridge) can drive and debug widgets in isolation. Wired by
8
+ * {@link runStorybook} when `GJSIFY_DEVTOOLS` is set.
9
+ */
10
+ export declare function storybookDevtoolsExtension(registry: StoryRegistryService, window: StorybookWindow): DevtoolsExtension;
@@ -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,5 +1,6 @@
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 { StoryModule, StoryWidget } from './story-widget.js';
3
4
  /**
4
5
  * Main window for the storybook. The sidebar lists story instances grouped by
5
6
  * the `Category/Name` title prefix; the preview pane holds the active
@@ -27,6 +28,20 @@ export declare class StorybookWindow extends Adw.ApplicationWindow {
27
28
  private _groupStoriesByCategory;
28
29
  private _addCategoryToSidebar;
29
30
  private _addStoryToSidebar;
31
+ /** The currently-displayed story, or null. */
32
+ get activeStory(): StoryWidget | null;
33
+ /**
34
+ * Select + show a story by its full `Category/Name` title; the sidebar
35
+ * selection updates too, so the UI stays in sync. Returns false when no
36
+ * story with that title exists.
37
+ */
38
+ openStoryByTitle(title: string): boolean;
39
+ /**
40
+ * Set one arg on the active story — drives the same `args` path as the
41
+ * control panel, so the visible control rows refresh via `notify::args`.
42
+ * Returns false when no story is active.
43
+ */
44
+ setActiveArg(name: string, value: StoryArgValue): boolean;
30
45
  private _onStorySelected;
31
46
  private _showStory;
32
47
  private _updateControlPanel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/storybook",
3
- "version": "0.10.0",
3
+ "version": "0.11.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,12 @@
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.11.0",
45
+ "@gjsify/stories": "^0.11.0"
45
46
  },
46
47
  "devDependencies": {
47
- "@gjsify/cli": "^0.10.0",
48
- "@gjsify/unit": "^0.10.0",
48
+ "@gjsify/cli": "^0.11.0",
49
+ "@gjsify/unit": "^0.11.0",
49
50
  "@types/node": "^25.9.2",
50
51
  "typescript": "^6.0.3"
51
52
  },