@pyreon/mcp 0.7.14 → 0.11.1

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/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@pyreon/mcp",
3
- "version": "0.7.14",
3
+ "version": "0.11.1",
4
4
  "description": "MCP server for Pyreon — AI-powered framework assistance",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/pyreon/pyreon.git",
9
- "directory": "packages/mcp"
9
+ "directory": "packages/tools/mcp"
10
10
  },
11
11
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/mcp#readme",
12
12
  "bugs": {
@@ -42,7 +42,7 @@
42
42
  "prepublishOnly": "bun run build"
43
43
  },
44
44
  "dependencies": {
45
- "@pyreon/compiler": "^0.7.14",
45
+ "@pyreon/compiler": "^0.11.1",
46
46
  "@modelcontextprotocol/sdk": "^1.27.1",
47
47
  "zod": "^3.25.76"
48
48
  },
@@ -570,4 +570,545 @@ hydrateRoot(<App />, document.getElementById("app")!)`,
570
570
  .fade-enter-from, .fade-leave-to { opacity: 0 }
571
571
  */`,
572
572
  },
573
+
574
+ // ═══════════════════════════════════════════════════════════════════════════
575
+ // @pyreon/store
576
+ // ═══════════════════════════════════════════════════════════════════════════
577
+
578
+ "store/defineStore": {
579
+ signature: "defineStore<T>(id: string, setup: () => T): () => StoreApi<T>",
580
+ example: `const useCounter = defineStore('counter', () => {
581
+ const count = signal(0)
582
+ const increment = () => count.update(n => n + 1)
583
+ return { count, increment }
584
+ })
585
+
586
+ const { store } = useCounter()
587
+ store.count() // 0
588
+ store.increment() // reactive update`,
589
+ notes:
590
+ "Composition-style stores. Singleton by ID. Returns StoreApi with .store, .patch(), .subscribe(), .onAction(), .reset(), .dispose().",
591
+ },
592
+
593
+ // ═══════════════════════════════════════════════════════════════════════════
594
+ // @pyreon/form
595
+ // ═══════════════════════════════════════════════════════════════════════════
596
+
597
+ "form/useForm": {
598
+ signature:
599
+ "useForm<T>(options: { initialValues: T, onSubmit: (values: T) => void | Promise<void>, schema?, validateOn?, debounceMs? }): FormInstance<T>",
600
+ example: `const form = useForm({
601
+ initialValues: { name: '', email: '' },
602
+ onSubmit: async (values) => await api.save(values),
603
+ validateOn: 'blur',
604
+ })
605
+
606
+ form.handleSubmit() // triggers validation + onSubmit
607
+ form.reset() // reset to initial values`,
608
+ notes:
609
+ "Signal-based form state. Use useField() for individual field binding, useFieldArray() for dynamic arrays.",
610
+ },
611
+
612
+ "form/useField": {
613
+ signature: "useField<T>(form: FormInstance<T>, name: keyof T): FieldInstance",
614
+ example: `const name = useField(form, 'name')
615
+
616
+ <input {...name.register()} />
617
+ // name.value(), name.error(), name.hasError(), name.showError()`,
618
+ },
619
+
620
+ // ═══════════════════════════════════════════════════════════════════════════
621
+ // @pyreon/query
622
+ // ═══════════════════════════════════════════════════════════════════════════
623
+
624
+ "query/useQuery": {
625
+ signature:
626
+ "useQuery<T>(options: { queryKey: unknown[], queryFn: () => Promise<T>, ... }): { data: Signal<T>, error: Signal<Error>, isFetching: Signal<boolean>, ... }",
627
+ example: `const { data, error, isFetching } = useQuery({
628
+ queryKey: ['users'],
629
+ queryFn: () => fetch('/api/users').then(r => r.json()),
630
+ })`,
631
+ notes:
632
+ "TanStack Query adapter. Fine-grained signals per field. Reactive options via function getter. Also: useMutation, useInfiniteQuery, useSuspenseQuery, useSubscription (WebSocket).",
633
+ },
634
+
635
+ // ═══════════════════════════════════════════════════════════════════════════
636
+ // @pyreon/permissions
637
+ // ═══════════════════════════════════════════════════════════════════════════
638
+
639
+ "permissions/createPermissions": {
640
+ signature: "createPermissions<T extends PermissionMap>(initial?: T): PermissionsInstance",
641
+ example: `const can = createPermissions({
642
+ 'posts.read': true,
643
+ 'posts.delete': (post) => post.authorId === userId,
644
+ 'admin.*': false,
645
+ })
646
+
647
+ can('posts.read') // true (reactive)
648
+ can('posts.delete', post) // evaluates predicate
649
+ can.not('admin.dashboard')
650
+ can.all('posts.read', 'posts.create')
651
+ can.any('admin.users', 'posts.read')`,
652
+ notes:
653
+ "Reactive permissions. Supports RBAC, ABAC, feature flags, subscription tiers. Wildcard matching with '*'. PermissionsProvider/usePermissions for context.",
654
+ },
655
+
656
+ // ═══════════════════════════════════════════════════════════════════════════
657
+ // @pyreon/machine
658
+ // ═══════════════════════════════════════════════════════════════════════════
659
+
660
+ "machine/createMachine": {
661
+ signature: "createMachine<S, E>(config: MachineConfig<S, E>): Machine<S, E>",
662
+ example: `const traffic = createMachine({
663
+ initial: 'red',
664
+ states: {
665
+ red: { on: { NEXT: 'green' } },
666
+ green: { on: { NEXT: 'yellow' } },
667
+ yellow: { on: { NEXT: 'red' } },
668
+ },
669
+ })
670
+
671
+ traffic() // 'red' (reactive)
672
+ traffic.send('NEXT') // 'green'
673
+ traffic.matches('green') // true
674
+ traffic.can('NEXT') // true`,
675
+ notes:
676
+ "Constrained signal with type-safe transitions. Guards: { target, guard: (payload?) => boolean }. No context — use signals alongside.",
677
+ },
678
+
679
+ // ═══════════════════════════════════════════════════════════════════════════
680
+ // @pyreon/storage
681
+ // ═══════════════════════════════════════════════════════════════════════════
682
+
683
+ "storage/useStorage": {
684
+ signature:
685
+ "useStorage<T>(key: string, defaultValue: T, options?: StorageOptions<T>): StorageSignal<T>",
686
+ example: `const theme = useStorage('theme', 'light')
687
+ theme() // 'light'
688
+ theme.set('dark') // persists + cross-tab sync
689
+ theme.remove() // delete from storage`,
690
+ notes:
691
+ "localStorage by default. Also: useSessionStorage, useCookie, useIndexedDB, useMemoryStorage, createStorage(backend). All return StorageSignal<T> extending Signal<T> with .remove().",
692
+ },
693
+
694
+ // ═══════════════════════════════════════════════════════════════════════════
695
+ // @pyreon/i18n
696
+ // ═══════════════════════════════════════════════════════════════════════════
697
+
698
+ "i18n/createI18n": {
699
+ signature:
700
+ "createI18n(options: { locale: string, messages: Record<string, Record<string, string>>, loader?, fallbackLocale?, pluralRules? }): I18nInstance",
701
+ example: `const i18n = createI18n({
702
+ locale: 'en',
703
+ messages: { en: { greeting: 'Hello, {{name}}!' } },
704
+ loader: (locale, ns) => import(\`./locales/\${locale}/\${ns}.json\`),
705
+ })
706
+
707
+ const { t, locale } = useI18n()
708
+ t('greeting', { name: 'World' }) // "Hello, World!"
709
+ locale.set('fr') // switch reactively`,
710
+ notes:
711
+ "Interpolation with {{name}}, pluralization with _one/_other suffixes. Namespace lazy loading. <Trans> component for rich JSX interpolation.",
712
+ },
713
+
714
+ // ═══════════════════════════════════════════════════════════════════════════
715
+ // @pyreon/document
716
+ // ═══════════════════════════════════════════════════════════════════════════
717
+
718
+ "document/createDocument": {
719
+ signature: "createDocument(props?: DocumentProps): DocumentBuilder",
720
+ example: `const doc = createDocument({ title: 'Report' })
721
+ .heading('Sales Report')
722
+ .table({ columns: ['Region', 'Revenue'], rows: [['US', '$1M']] })
723
+
724
+ await doc.toPdf() // PDF
725
+ await doc.toEmail() // Outlook-safe HTML
726
+ await doc.toDocx() // Word document
727
+ await doc.toSlack() // Slack Block Kit JSON
728
+ await doc.toNotion() // Notion blocks`,
729
+ notes:
730
+ "14+ output formats. JSX primitives: Document, Page, Heading, Text, Table, Image, List, Code, etc. Heavy renderers lazy-loaded.",
731
+ },
732
+
733
+ // ═══════════════════════════════════════════════════════════════════════════
734
+ // @pyreon/flow
735
+ // ═══════════════════════════════════════════════════════════════════════════
736
+
737
+ "flow/createFlow": {
738
+ signature: "createFlow(config: { nodes: FlowNode[], edges: FlowEdge[], ... }): FlowInstance",
739
+ example: `const flow = createFlow({
740
+ nodes: [
741
+ { id: '1', position: { x: 0, y: 0 }, data: { label: 'Start' } },
742
+ { id: '2', position: { x: 200, y: 100 }, data: { label: 'End' } },
743
+ ],
744
+ edges: [{ id: 'e1', source: '1', target: '2' }],
745
+ })
746
+
747
+ flow.addNode({ id: '3', position: { x: 100, y: 200 }, data: { label: 'New' } })
748
+ await flow.layout('layered') // auto-layout via elkjs
749
+
750
+ <Flow instance={flow}><Background /><Controls /><MiniMap /></Flow>`,
751
+ notes:
752
+ "Signal-native nodes/edges. Auto-layout via elkjs (lazy-loaded). Pan/zoom via pointer events + CSS transforms. No D3.",
753
+ },
754
+
755
+ // ═══════════════════════════════════════════════════════════════════════════
756
+ // @pyreon/code
757
+ // ═══════════════════════════════════════════════════════════════════════════
758
+
759
+ "code/createEditor": {
760
+ signature:
761
+ "createEditor(config: { value?: string, language?: string, theme?: string, minimap?: boolean, ... }): EditorInstance",
762
+ example: `const editor = createEditor({
763
+ value: '// hello',
764
+ language: 'typescript',
765
+ theme: 'dark',
766
+ minimap: true,
767
+ })
768
+
769
+ editor.value() // reactive Signal<string>
770
+ editor.goToLine(42)
771
+ editor.insert('new code')
772
+
773
+ <CodeEditor instance={editor} />
774
+ <DiffEditor original="old" modified="new" />`,
775
+ notes:
776
+ "Built on CodeMirror 6 (~250KB vs Monaco's ~2.5MB). loadLanguage() for lazy grammars. TabbedEditor for multi-file.",
777
+ },
778
+
779
+ // ═══════════════════════════════════════════════════════════════════════════
780
+ // @pyreon/hotkeys
781
+ // ═══════════════════════════════════════════════════════════════════════════
782
+
783
+ "hotkeys/useHotkey": {
784
+ signature:
785
+ "useHotkey(shortcut: string, handler: (e: KeyboardEvent) => void, options?: HotkeyOptions): void",
786
+ example: `useHotkey('mod+s', (e) => {
787
+ e.preventDefault()
788
+ save()
789
+ })
790
+
791
+ useHotkey('mod+k', () => openSearch(), { scope: 'global' })
792
+ useHotkeyScope('editor') // activate scope for component lifetime`,
793
+ notes:
794
+ "Component-scoped, auto-unregisters on unmount. 'mod' = ⌘ on Mac, Ctrl elsewhere. Scope-based activation for context-aware shortcuts.",
795
+ },
796
+
797
+ // ═══════════════════════════════════════════════════════════════════════════
798
+ // @pyreon/table
799
+ // ═══════════════════════════════════════════════════════════════════════════
800
+
801
+ "table/useTable": {
802
+ signature: "useTable<T>(options: TableOptions<T>): Table<T>",
803
+ example: `const table = useTable({
804
+ data: () => users(),
805
+ columns: [
806
+ { accessorKey: 'name', header: 'Name' },
807
+ { accessorKey: 'email', header: 'Email' },
808
+ ],
809
+ })
810
+
811
+ // flexRender for column templates:
812
+ flexRender(cell.column.columnDef.cell, cell.getContext())`,
813
+ notes: "TanStack Table adapter with reactive options and auto state sync.",
814
+ },
815
+
816
+ // ═══════════════════════════════════════════════════════════════════════════
817
+ // @pyreon/virtual
818
+ // ═══════════════════════════════════════════════════════════════════════════
819
+
820
+ "virtual/useVirtualizer": {
821
+ signature:
822
+ "useVirtualizer(options: VirtualizerOptions): { virtualItems: Signal, totalSize: Signal, scrollToIndex: (i) => void, ... }",
823
+ example: `const { virtualItems, totalSize } = useVirtualizer({
824
+ count: 10000,
825
+ getScrollElement: () => scrollRef.current,
826
+ estimateSize: () => 35,
827
+ })`,
828
+ notes: "TanStack Virtual adapter. Also: useWindowVirtualizer for window-scoped virtualization.",
829
+ },
830
+
831
+ // ═══════════════════════════════════════════════════════════════════════════
832
+ // @pyreon/feature
833
+ // ═══════════════════════════════════════════════════════════════════════════
834
+
835
+ "feature/defineFeature": {
836
+ signature:
837
+ "defineFeature<T>(config: { name: string, schema: FeatureSchema<T>, api: FeatureApi<T> }): Feature<T>",
838
+ example: `const Posts = defineFeature({
839
+ name: 'posts',
840
+ schema: { title: 'string', body: 'string', author: reference('users') },
841
+ api: { baseUrl: '/api/posts' },
842
+ })
843
+
844
+ // Auto-generated hooks:
845
+ Posts.useList() // paginated query
846
+ Posts.useById(id) // single item query
847
+ Posts.useCreate() // mutation
848
+ Posts.useForm(id) // edit form with validation
849
+ Posts.useTable() // TanStack Table config`,
850
+ notes:
851
+ "Schema-driven CRUD. Composes @pyreon/query, @pyreon/form, @pyreon/validation, @pyreon/store, @pyreon/table.",
852
+ },
853
+
854
+ // ═══════════════════════════════════════════════════════════════════════════
855
+ // @pyreon/storybook
856
+ // ═══════════════════════════════════════════════════════════════════════════
857
+
858
+ // ═══════════════════════════════════════════════════════════════════════════
859
+ // @pyreon/lint
860
+ // ═══════════════════════════════════════════════════════════════════════════
861
+
862
+ "lint/lint": {
863
+ signature: "lint(options?: LintOptions): LintResult",
864
+ example: `import { lint } from "@pyreon/lint"
865
+
866
+ const result = lint({ paths: ["src/"], preset: "recommended" })
867
+ console.log(result.totalErrors, result.totalWarnings)
868
+
869
+ // With config file auto-loading + rule overrides
870
+ lint({ paths: ["."], ruleOverrides: { "pyreon/no-classname": "off" } })`,
871
+ notes:
872
+ "Programmatic API. 55 rules across 12 categories. Auto-loads .pyreonlintrc.json. Presets: recommended, strict, app, lib. Uses oxc-parser with AST caching.",
873
+ },
874
+
875
+ "lint/lintFile": {
876
+ signature:
877
+ "lintFile(filePath: string, sourceText: string, rules: Rule[], config: LintConfig, cache?: AstCache): LintFileResult",
878
+ example: `import { lintFile, allRules, getPreset, AstCache } from "@pyreon/lint"
879
+
880
+ const cache = new AstCache()
881
+ const config = getPreset("recommended")
882
+ const result = lintFile("app.tsx", source, allRules, config, cache)`,
883
+ notes: "Low-level single-file API. Optional AstCache for repeat runs (FNV-1a hash keyed).",
884
+ },
885
+
886
+ "lint/cli": {
887
+ signature:
888
+ "pyreon-lint [--preset name] [--fix] [--format text|json|compact] [--quiet] [--watch] [--list] [--config path] [--ignore path] [--rule id=severity] [path...]",
889
+ example: `pyreon-lint --preset strict --quiet # CI mode
890
+ pyreon-lint --fix # auto-fix
891
+ pyreon-lint --watch src/ # watch mode
892
+ pyreon-lint --list # list all 55 rules
893
+ pyreon-lint --format json # machine-readable`,
894
+ notes:
895
+ "CLI entry. Config: .pyreonlintrc.json, package.json 'pyreonlint' field. Ignore: .pyreonlintignore + .gitignore. Watch: fs.watch recursive with 100ms debounce.",
896
+ },
897
+
898
+ // ═══════════════════════════════════════════════════════════════════════════
899
+ // @pyreon/ui-core
900
+ // ═══════════════════════════════════════════════════════════════════════════
901
+
902
+ "ui-core/PyreonUI": {
903
+ signature:
904
+ "PyreonUI(props: { theme?: Theme; mode?: 'light' | 'dark' | 'system'; inversed?: boolean; children: VNodeChild }): VNodeChild",
905
+ example: `import { PyreonUI } from "@pyreon/ui-core"
906
+ import { enrichTheme } from "@pyreon/unistyle"
907
+
908
+ const theme = enrichTheme({ colors: { primary: "#3b82f6" } })
909
+
910
+ <PyreonUI theme={theme} mode="system">
911
+ <App />
912
+ </PyreonUI>
913
+
914
+ // mode="system" auto-detects OS dark mode via prefers-color-scheme
915
+ // inversed flips the resolved mode (light↔dark)`,
916
+ notes:
917
+ "Unified provider replacing 3 separate providers (theme, mode, config). Calls init() internally. mode='system' uses matchMedia('(prefers-color-scheme: dark)') and reactively updates.",
918
+ mistakes: `- Using ThemeProvider + ModeProvider + ConfigProvider separately → Use PyreonUI instead
919
+ - Forgetting enrichTheme() → raw theme objects miss default breakpoints/spacing`,
920
+ },
921
+
922
+ "ui-core/useMode": {
923
+ signature: "useMode(): Signal<'light' | 'dark'>",
924
+ example: `import { useMode } from "@pyreon/ui-core"
925
+
926
+ const mode = useMode()
927
+ // mode() returns "light" or "dark" (resolved, reactive)
928
+ // Reflects OS preference when PyreonUI mode="system"`,
929
+ notes:
930
+ "Returns the resolved mode as a reactive signal. When mode='system', reflects the OS preference. When inversed is true, the mode is flipped.",
931
+ },
932
+
933
+ // ═══════════════════════════════════════════════════════════════════════════
934
+ // @pyreon/unistyle
935
+ // ═══════════════════════════════════════════════════════════════════════════
936
+
937
+ "unistyle/enrichTheme": {
938
+ signature: "enrichTheme(theme: PartialTheme): Theme",
939
+ example: `import { enrichTheme } from "@pyreon/unistyle"
940
+
941
+ const theme = enrichTheme({
942
+ colors: { primary: "#3b82f6", secondary: "#6366f1" },
943
+ fonts: { body: "Inter, sans-serif" },
944
+ })
945
+
946
+ // Merges user overrides with default breakpoints, spacing, and units`,
947
+ notes:
948
+ "Merges a partial theme with the full default theme (breakpoints, spacing, unit utilities). Always use when passing a theme to PyreonUI.",
949
+ },
950
+
951
+ // ═══════════════════════════════════════════════════════════════════════════
952
+ // @pyreon/storybook
953
+ // ═══════════════════════════════════════════════════════════════════════════
954
+
955
+ // ═══════════════════════════════════════════════════════════════════════════
956
+ // @pyreon/rx
957
+ // ═══════════════════════════════════════════════════════════════════════════
958
+
959
+ "rx/filter": {
960
+ signature:
961
+ "filter<T>(source: Signal<T[]> | T[], predicate: (item: T) => boolean): Computed<T[]> | T[]",
962
+ example: `import { filter } from '@pyreon/rx'
963
+
964
+ // Signal input → Computed output (auto-tracks):
965
+ const items = signal([1, 2, 3, 4, 5])
966
+ const evens = filter(items, n => n % 2 === 0) // Computed<number[]>
967
+ evens() // [2, 4]
968
+
969
+ // Plain input → plain output:
970
+ const result = filter([1, 2, 3, 4, 5], n => n > 3) // [4, 5]`,
971
+ notes:
972
+ "Every @pyreon/rx function is overloaded: Signal<T[]> input produces Computed<T[]>, plain T[] input produces plain T[]. 24 functions total: filter, map, sortBy, groupBy, keyBy, uniqBy, take, skip, last, chunk, flatten, find, mapValues, count, sum, min, max, average, distinct, scan, combine, debounce, throttle, search.",
973
+ },
974
+
975
+ "rx/pipe": {
976
+ signature: "pipe<T>(source: Signal<T[]> | T[], ...operators: Operator[]): Computed<T[]> | T[]",
977
+ example: `import { pipe, filter, sortBy, map } from '@pyreon/rx'
978
+
979
+ const users = signal([
980
+ { name: 'Charlie', age: 35 },
981
+ { name: 'Alice', age: 25 },
982
+ { name: 'Bob', age: 30 },
983
+ ])
984
+
985
+ // Compose transforms left-to-right:
986
+ const result = pipe(
987
+ users,
988
+ filter(u => u.age >= 30),
989
+ sortBy('name'),
990
+ map(u => u.name),
991
+ )
992
+ // Computed<string[]> → ["Bob", "Charlie"]`,
993
+ notes:
994
+ "Pipe composes operators left-to-right. Signal source produces reactive Computed that re-derives when source changes.",
995
+ },
996
+
997
+ // ═══════════════════════════════════════════════════════════════════════════
998
+ // @pyreon/toast
999
+ // ═══════════════════════════════════════════════════════════════════════════
1000
+
1001
+ "toast/toast": {
1002
+ signature:
1003
+ "toast(message: string, options?: ToastOptions): string\ntoast.success/error/warning/info/loading(message): string\ntoast.update(id, options): void\ntoast.dismiss(id?): void\ntoast.promise(promise, { loading, success, error }): string",
1004
+ example: `import { toast, Toaster } from '@pyreon/toast'
1005
+
1006
+ // Basic:
1007
+ toast('Hello!')
1008
+ toast.success('Saved!')
1009
+ toast.error('Failed!')
1010
+
1011
+ // Loading → success pattern:
1012
+ const id = toast.loading('Saving...')
1013
+ await save()
1014
+ toast.update(id, { type: 'success', message: 'Done!' })
1015
+
1016
+ // Promise helper:
1017
+ toast.promise(fetchData(), {
1018
+ loading: 'Loading...',
1019
+ success: 'Loaded!',
1020
+ error: 'Failed to load',
1021
+ })
1022
+
1023
+ // Dismiss:
1024
+ toast.dismiss(id) // one
1025
+ toast.dismiss() // all
1026
+
1027
+ // Mount Toaster once in your app:
1028
+ <Toaster />`,
1029
+ notes:
1030
+ "Imperative API — call from anywhere, no context needed. <Toaster /> renders via Portal with CSS transitions, auto-dismiss, pause on hover. Accessible: role='alert', aria-live='polite'.",
1031
+ },
1032
+
1033
+ // ═══════════════════════════════════════════════════════════════════════════
1034
+ // @pyreon/url-state
1035
+ // ═══════════════════════════════════════════════════════════════════════════
1036
+
1037
+ "url-state/useUrlState": {
1038
+ signature:
1039
+ "useUrlState<T>(key: string, defaultValue: T): UrlStateSignal<T>\nuseUrlState<T extends Record<string, unknown>>(schema: T): UrlStateSchema<T>",
1040
+ example: `import { useUrlState } from '@pyreon/url-state'
1041
+
1042
+ // Single param — synced to ?page=:
1043
+ const page = useUrlState('page', 1)
1044
+ page() // 1 (auto-coerced number)
1045
+ page.set(2) // URL → ?page=2
1046
+
1047
+ // Schema mode — multiple params:
1048
+ const filters = useUrlState({ page: 1, sort: 'name', desc: false })
1049
+ filters.page() // 1
1050
+ filters.sort() // "name"
1051
+ filters.set({ page: 2, sort: 'date' })`,
1052
+ notes:
1053
+ "Auto type coercion (numbers, booleans, arrays). Uses replaceState (no history spam). Configurable debounce. SSR-safe — reads request URL on server.",
1054
+ },
1055
+
1056
+ // ═══════════════════════════════════════════════════════════════════════════
1057
+ // @pyreon/query — useSSE
1058
+ // ═══════════════════════════════════════════════════════════════════════════
1059
+
1060
+ "query/useSSE": {
1061
+ signature:
1062
+ "useSSE<T>(options: { queryKey: unknown[], url: string, transform?: (event: MessageEvent) => T, ... }): { data: Signal<T>, error: Signal<Error>, status: Signal<string> }",
1063
+ example: `import { useSSE } from '@pyreon/query'
1064
+
1065
+ const { data, error, status } = useSSE({
1066
+ queryKey: ['events'],
1067
+ url: '/api/events',
1068
+ transform: (event) => JSON.parse(event.data),
1069
+ })
1070
+
1071
+ // data() reactively updates on each SSE message
1072
+ // Auto-reconnects on disconnect
1073
+ // Integrates with QueryClient for cache invalidation`,
1074
+ notes:
1075
+ "Server-Sent Events hook. Same pattern as useSubscription but read-only (no send). Integrates with QueryClient cache.",
1076
+ },
1077
+
1078
+ // ═══════════════════════════════════════════════════════════════════════════
1079
+ // @pyreon/router — useIsActive
1080
+ // ═══════════════════════════════════════════════════════════════════════════
1081
+
1082
+ "router/useIsActive": {
1083
+ signature: "useIsActive(path: string, exact?: boolean): () => boolean",
1084
+ example: `import { useIsActive } from '@pyreon/router'
1085
+
1086
+ const isHome = useIsActive('/')
1087
+ const isAdmin = useIsActive('/admin') // prefix match
1088
+ const isExactAdmin = useIsActive('/admin', true) // exact only
1089
+
1090
+ // Reactive — updates when route changes:
1091
+ <a class={{ active: isAdmin() }} href="/admin">Admin</a>`,
1092
+ notes:
1093
+ "Returns a reactive boolean. Segment-aware prefix matching: /admin matches /admin/users but not /admin-panel. Pass exact=true for exact-only matching.",
1094
+ },
1095
+
1096
+ "storybook/renderToCanvas": {
1097
+ signature: "renderToCanvas(context: StoryContext, canvasElement: HTMLElement): void",
1098
+ example: `// .storybook/main.ts:
1099
+ export default { framework: '@pyreon/storybook' }
1100
+
1101
+ // Story file:
1102
+ import type { Meta, StoryObj } from '@pyreon/storybook'
1103
+ import { Button } from './Button'
1104
+
1105
+ const meta: Meta<typeof Button> = { component: Button }
1106
+ export default meta
1107
+
1108
+ export const Primary: StoryObj<typeof meta> = {
1109
+ args: { variant: 'primary', label: 'Click me' },
1110
+ }`,
1111
+ notes:
1112
+ "Storybook renderer for Pyreon components. Re-exports h, Fragment, signal, computed, effect, mount for story convenience.",
1113
+ },
573
1114
  }