@stacksjs/defaults 0.70.358 → 0.70.363
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/ai/skills/stacks-browse/SKILL.md +7 -5
- package/ai/skills/stacks-browse/scripts/browse.ts +68 -9
- package/ai/skills/stacks-dashboard/SKILL.md +4 -0
- package/app/Actions/AI/AskAction.ts +12 -17
- package/app/Actions/AI/SummaryAction.ts +12 -17
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +4 -1
- package/app/Actions/Dashboard/Models/model-write.ts +5 -0
- package/app/Actions/LogAction.ts +11 -11
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Buttons/Counter.stx +1 -1
- package/resources/components/Buttons/ToggleDark.stx +1 -1
- package/resources/components/Dashboard/App/RequestDetailsDialog.stx +42 -0
- package/resources/components/Dashboard/App/RequestsOverview.stx +1 -21
- package/resources/components/Dashboard/Auth/AccessTokenCreateDialog.stx +155 -0
- package/resources/components/Dashboard/Auth/AccessTokens.stx +41 -129
- package/resources/components/Dashboard/Ci/CiDashboard.stx +1 -157
- package/resources/components/Dashboard/Ci/CiRunHistoryDrawer.stx +127 -0
- package/resources/components/Dashboard/Commerce/CommerceCategoryDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCategoryDetailsDialog.stx +22 -17
- package/resources/components/Dashboard/Commerce/CommerceCategoryDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceCouponDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/CommerceCouponDetailsDialog.stx +25 -19
- package/resources/components/Dashboard/Commerce/CommerceCouponDialog.stx +14 -11
- package/resources/components/Dashboard/Commerce/CommerceCustomerDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceCustomerDetailsDialog.stx +24 -16
- package/resources/components/Dashboard/Commerce/CommerceCustomerDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/CommerceGiftCardDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDeleteDialog.stx +14 -16
- package/resources/components/Dashboard/Commerce/CommerceManufacturerDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceOrderDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceOrderDetailsDialog.stx +15 -10
- package/resources/components/Dashboard/Commerce/CommerceOrderDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommercePosCheckoutDialog.stx +30 -25
- package/resources/components/Dashboard/Commerce/CommercePosReceiptDialog.stx +27 -20
- package/resources/components/Dashboard/Commerce/CommerceProductDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceProductDialog.stx +14 -13
- package/resources/components/Dashboard/Commerce/CommerceReviewDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/CommerceReviewEditDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxDeleteDialog.stx +14 -14
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +1 -1
- package/resources/components/Dashboard/Commerce/CommerceUnitDeleteDialog.stx +14 -17
- package/resources/components/Dashboard/Commerce/CommerceUnitDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/CommerceVariantDeleteDialog.stx +10 -8
- package/resources/components/Dashboard/Commerce/CommerceVariantDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DigitalDeliveryDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/DriverDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/DriverDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/LicenseKeyDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingMethodDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingRateDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDeleteDialog.stx +11 -15
- package/resources/components/Dashboard/Commerce/Delivery/ShippingZoneDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PaymentDetailsDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PaymentRefundDialog.stx +16 -15
- package/resources/components/Dashboard/Commerce/PrintDeviceDeleteDialog.stx +10 -11
- package/resources/components/Dashboard/Commerce/PrintDeviceDetailsDialog.stx +16 -14
- package/resources/components/Dashboard/Commerce/PrintDeviceDialog.stx +17 -16
- package/resources/components/Dashboard/Commerce/PrintLogDeleteDialog.stx +10 -7
- package/resources/components/Dashboard/Commerce/PrintLogDetailsDialog.stx +13 -10
- package/resources/components/Dashboard/Commerce/ProductWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/ProductWaitlistEntryDialog.stx +96 -105
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDeleteDialog.stx +10 -18
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistEntryDialog.stx +92 -95
- package/resources/components/Dashboard/Content/AuthorEditorDialog.stx +94 -0
- package/resources/components/Dashboard/Content/AuthorsDashboard.stx +39 -31
- package/resources/components/Dashboard/Content/BlogDashboard.stx +87 -176
- package/resources/components/Dashboard/Content/BlogPostEditorDialog.stx +195 -0
- package/resources/components/Dashboard/Content/CommentsDashboard.stx +14 -4
- package/resources/components/Dashboard/Content/ContentTaxonomyDashboard.stx +40 -55
- package/resources/components/Dashboard/Content/CreateFolderDialog.stx +45 -0
- package/resources/components/Dashboard/Content/FileDetailsDrawer.stx +31 -0
- package/resources/components/Dashboard/Content/FileManagerDashboard.stx +47 -107
- package/resources/components/Dashboard/Content/FileUploadDialog.stx +89 -0
- package/resources/components/Dashboard/Content/PageEditorDialog.stx +93 -0
- package/resources/components/Dashboard/Content/PagesDashboard.stx +36 -26
- package/resources/components/Dashboard/Content/PostEditorDialog.stx +176 -0
- package/resources/components/Dashboard/Content/PostsDashboard.stx +40 -64
- package/resources/components/Dashboard/Content/SeoDashboard.stx +18 -84
- package/resources/components/Dashboard/Content/SeoEditorDialog.stx +108 -0
- package/resources/components/Dashboard/Content/TaxonomyEditorDialog.stx +106 -0
- package/resources/components/Dashboard/CreateRecordModal.stx +72 -83
- package/resources/components/Dashboard/Email/InboxMessageList.stx +1 -1
- package/resources/components/Dashboard/GlobalSearch.stx +3 -9
- package/resources/components/Dashboard/Infrastructure/CloudDetailsDialog.stx +2 -16
- package/resources/components/Dashboard/Infrastructure/DnsRecordDialog.stx +4 -16
- package/resources/components/Dashboard/Infrastructure/LogDetailsDialog.stx +4 -19
- package/resources/components/Dashboard/Infrastructure/MailboxDetailsDialog.stx +6 -16
- package/resources/components/Dashboard/Infrastructure/ServerInspectorDialog.stx +105 -0
- package/resources/components/Dashboard/Infrastructure/ServersDashboard.stx +5 -59
- package/resources/components/Dashboard/Jobs/JobHistory.stx +1 -11
- package/resources/components/Dashboard/Library/ComponentsDashboard.stx +11 -60
- package/resources/components/Dashboard/Library/FunctionsDashboard.stx +11 -15
- package/resources/components/Dashboard/Library/SourceScaffoldDialog.stx +95 -0
- package/resources/components/Dashboard/Management/AssignmentDrawer.stx +93 -0
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +47 -129
- package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +11 -12
- package/resources/components/Dashboard/Marketing/CampaignDeliveryDialog.stx +5 -8
- package/resources/components/Dashboard/Marketing/CampaignDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +15 -15
- package/resources/components/Dashboard/Marketing/MarketingListDialog.stx +17 -17
- package/resources/components/Dashboard/Marketing/MarketingListsTable.stx +1 -1
- package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +11 -12
- package/resources/components/Dashboard/Marketing/SocialPostDialog.stx +17 -16
- package/resources/components/Dashboard/Models/EditRecordModal.stx +81 -0
- package/resources/components/Dashboard/Models/ModelRecordActions.stx +30 -0
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +42 -94
- package/resources/components/Dashboard/Navbar.stx +5 -5
- package/resources/components/Dashboard/Notifications/NotificationDeliveryDialog.stx +40 -60
- package/resources/components/Dashboard/UI/Button.stx +4 -0
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +55 -53
- package/resources/components/Dashboard/UI/Drawer.stx +114 -0
- package/resources/components/Dashboard/UI/Modal.stx +32 -21
- package/resources/components/Docs/Demo/ComboboxDemo.stx +4 -5
- package/resources/components/Docs/Demo/DropdownDemo.stx +12 -5
- package/resources/components/Docs/Demo/ListboxDemo.stx +14 -6
- package/resources/components/Docs/Demo/PopoverDemo.stx +10 -5
- package/resources/components/Docs/Demo/RadioGroupDemo.stx +14 -6
- package/resources/components/Docs/Demo/StepperDemo.stx +4 -5
- package/resources/components/Docs/TeamMember.stx +16 -9
- package/resources/components/Forum/ForumReplyForm.stx +7 -7
- package/resources/components/Forum/ForumSidebar.stx +10 -6
- package/resources/components/Marketing/Faqs.stx +5 -5
- package/resources/components/Marketing/Feature.stx +11 -6
- package/resources/components/Marketing/Layout.stx +12 -8
- package/resources/components/Marketing/Pricing.stx +11 -5
- package/resources/components/Marketing/PrimaryFeatures.stx +4 -5
- package/resources/components/Marketing/PrimaryFeatures2.stx +6 -5
- package/resources/components/Marketing/SecondaryFeatures.stx +10 -5
- package/resources/components/Marketing/Testimonials.stx +8 -5
- package/resources/views/index.stx +4 -4
- package/views/system-tray/index.stx +1 -1
|
@@ -101,6 +101,7 @@ Extracts headings, links (`text -> href`), buttons, forms (action + field count)
|
|
|
101
101
|
bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts scenario <url> \
|
|
102
102
|
--step '{"action":"click","selector":"button[data-open]"}' \
|
|
103
103
|
--step '{"action":"focus","selector":"input[name=name]"}' \
|
|
104
|
+
--step '{"action":"evaluate","expression":"window.__stxDevtools?.stats()"}' \
|
|
104
105
|
--step '{"action":"fill","selector":"input[name=name]","value":"Example"}' \
|
|
105
106
|
--step '{"action":"click","selector":"button[type=submit]"}' \
|
|
106
107
|
--step '{"action":"assert","selector":"main","text":"Saved"}' \
|
|
@@ -108,16 +109,16 @@ bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts scenari
|
|
|
108
109
|
--out storage/framework/runtime/shots/scenario.png
|
|
109
110
|
```
|
|
110
111
|
|
|
111
|
-
Steps run in order within one isolated browser page, preserving reactive STX state and SPA navigation. Supported actions are `click`, `fill`, `focus`, `press`, `wait`, and `assert`. Each step is a JSON object passed through a repeatable `--step` flag. Click and focus steps may include `text` to select the matching control from their CSS selector. The command reports every completed step, the final URL and page text, console messages, console exceptions, failed requests, and an optional screenshot. It exits nonzero when the scenario assertion, browser console, or network fails.
|
|
112
|
+
Steps run in order within one isolated browser page, preserving reactive STX state and SPA navigation. Supported actions are `click`, `fill`, `focus`, `press`, `wait`, `evaluate`, and `assert`. Each step is a JSON object passed through a repeatable `--step` flag. Click and focus steps may include `text` to select the matching control from their CSS selector. They scroll matched controls into view before interacting, including controls inside horizontal overflow regions. Evaluate steps run a JavaScript expression in the isolated page and return its serialized value, which is useful for inspecting STX signals and DevTools state between interactions. The command reports every completed step, the final URL and page text, console messages, console exceptions, failed requests, and an optional screenshot. It exits nonzero when the scenario assertion, evaluation, browser console, or network fails.
|
|
112
113
|
Use `{"action":"assert","selector":"...","absent":true}` to verify that an element is missing or hidden after an interaction.
|
|
113
114
|
Use `{"action":"assert","selector":"button","text":"Save","focused":true}` to verify keyboard focus and focus restoration.
|
|
114
115
|
|
|
115
116
|
### Crawl (whole-site browser audit)
|
|
116
117
|
```bash
|
|
117
|
-
bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts crawl <url> [--viewport 1280x900] [--max 500] [--path /extra-route] [--settle 350] [--progress]
|
|
118
|
+
bun storage/framework/defaults/ai/skills/stacks-browse/scripts/browse.ts crawl <url> [--viewport 1280x900] [--max 500] [--path /extra-route] [--settle 350] [--progress] [--summary]
|
|
118
119
|
```
|
|
119
|
-
Uses a fresh isolated page target per route,
|
|
120
|
-
session
|
|
120
|
+
Uses a fresh isolated page target per route, waits for each target to fully close,
|
|
121
|
+
and relaunches Chromium if its DevTools session or target lifecycle fails during a long audit, while
|
|
121
122
|
following every same-origin link it discovers.
|
|
122
123
|
The viewport is deterministic and defaults to `1280x900`; pass `--viewport 768x1024`
|
|
123
124
|
to repeat the same route audit at a tablet breakpoint.
|
|
@@ -125,7 +126,8 @@ Each page is checked for a non-200 document, console exceptions, failed
|
|
|
125
126
|
requests, and horizontal overflow. Repeat `--path` to seed routes that are not
|
|
126
127
|
linked from the starting page. The command prints every crawled path and exits
|
|
127
128
|
nonzero when any page fails. Add `--progress` to stream each completed page
|
|
128
|
-
during a long audit.
|
|
129
|
+
during a long audit. Add `--summary` to report totals and compact failure
|
|
130
|
+
diagnostics without printing every visited path or overflowing element.
|
|
129
131
|
|
|
130
132
|
## Stacks-Specific QA
|
|
131
133
|
|
|
@@ -264,12 +264,38 @@ async function createPage(port: number): Promise<BrowserPage> {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
async function closePage(port: number, page: BrowserPage): Promise<
|
|
268
|
-
page.cdp.close()
|
|
267
|
+
async function closePage(port: number, page: BrowserPage): Promise<boolean> {
|
|
269
268
|
try {
|
|
270
|
-
await fetch(`http://127.0.0.1:${port}/json/close/${encodeURIComponent(page.targetId)}`)
|
|
269
|
+
const response = await fetch(`http://127.0.0.1:${port}/json/close/${encodeURIComponent(page.targetId)}`)
|
|
270
|
+
if (!response.ok) {
|
|
271
|
+
page.cdp.close()
|
|
272
|
+
return false
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
page.cdp.close()
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
page.cdp.close()
|
|
280
|
+
|
|
281
|
+
// Chromium acknowledges target closure before every renderer/helper has
|
|
282
|
+
// actually left the target list. Creating the next target immediately can
|
|
283
|
+
// accumulate closing renderers across a large crawl, eventually dropping
|
|
284
|
+
// Network and Runtime events. Wait for the target to disappear so each route
|
|
285
|
+
// starts with the isolation the command promises.
|
|
286
|
+
for (let attempt = 0; attempt < 40; attempt++) {
|
|
287
|
+
try {
|
|
288
|
+
const targets = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json() as any[]
|
|
289
|
+
if (!targets.some(target => target.id === page.targetId))
|
|
290
|
+
return true
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return false
|
|
294
|
+
}
|
|
295
|
+
await Bun.sleep(25)
|
|
271
296
|
}
|
|
272
|
-
|
|
297
|
+
|
|
298
|
+
return false
|
|
273
299
|
}
|
|
274
300
|
|
|
275
301
|
function kill(s: Session): void {
|
|
@@ -497,7 +523,7 @@ function parseViewport(value: string | undefined): { w: number, h: number } {
|
|
|
497
523
|
return { w, h }
|
|
498
524
|
}
|
|
499
525
|
|
|
500
|
-
type ScenarioAction = 'assert' | 'click' | 'fill' | 'focus' | 'press' | 'wait'
|
|
526
|
+
type ScenarioAction = 'assert' | 'click' | 'evaluate' | 'fill' | 'focus' | 'press' | 'wait'
|
|
501
527
|
|
|
502
528
|
interface ScenarioStep {
|
|
503
529
|
action: ScenarioAction
|
|
@@ -505,6 +531,7 @@ interface ScenarioStep {
|
|
|
505
531
|
text?: string
|
|
506
532
|
absent?: boolean
|
|
507
533
|
focused?: boolean
|
|
534
|
+
expression?: string
|
|
508
535
|
value?: string
|
|
509
536
|
key?: string
|
|
510
537
|
ms?: number
|
|
@@ -524,7 +551,7 @@ function parseScenarioStep(value: string): ScenarioStep {
|
|
|
524
551
|
throw new TypeError('Each scenario step must be a JSON object.')
|
|
525
552
|
|
|
526
553
|
const step = parsed as Record<string, unknown>
|
|
527
|
-
if (!['assert', 'click', 'fill', 'focus', 'press', 'wait'].includes(String(step.action)))
|
|
554
|
+
if (!['assert', 'click', 'evaluate', 'fill', 'focus', 'press', 'wait'].includes(String(step.action)))
|
|
528
555
|
throw new TypeError(`Unsupported scenario action: ${String(step.action)}`)
|
|
529
556
|
|
|
530
557
|
const action = step.action as ScenarioAction
|
|
@@ -532,6 +559,8 @@ function parseScenarioStep(value: string): ScenarioStep {
|
|
|
532
559
|
throw new TypeError(`Scenario action "${action}" requires a selector.`)
|
|
533
560
|
if (action === 'fill' && typeof step.value !== 'string')
|
|
534
561
|
throw new TypeError('Scenario action "fill" requires a string value.')
|
|
562
|
+
if (action === 'evaluate' && typeof step.expression !== 'string')
|
|
563
|
+
throw new TypeError('Scenario action "evaluate" requires an expression.')
|
|
535
564
|
if (step.absent !== undefined && (action !== 'assert' || typeof step.absent !== 'boolean'))
|
|
536
565
|
throw new TypeError('Scenario "absent" is a boolean supported only by assert actions.')
|
|
537
566
|
if (step.focused !== undefined && (action !== 'assert' || typeof step.focused !== 'boolean'))
|
|
@@ -558,6 +587,17 @@ async function runScenarioStep(cdp: Cdp, step: ScenarioStep): Promise<Record<str
|
|
|
558
587
|
return { action: step.action, key, ok: true }
|
|
559
588
|
}
|
|
560
589
|
|
|
590
|
+
if (step.action === 'evaluate') {
|
|
591
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
592
|
+
expression: step.expression,
|
|
593
|
+
returnByValue: true,
|
|
594
|
+
awaitPromise: true,
|
|
595
|
+
})
|
|
596
|
+
if (result.exceptionDetails)
|
|
597
|
+
throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text || 'Evaluation failed')
|
|
598
|
+
return { action: step.action, value: result.result?.value, ok: true }
|
|
599
|
+
}
|
|
600
|
+
|
|
561
601
|
const result = await cdp.send('Runtime.evaluate', {
|
|
562
602
|
expression: `(() => {
|
|
563
603
|
const step = ${JSON.stringify(step)}
|
|
@@ -572,6 +612,8 @@ async function runScenarioStep(cdp: Cdp, step: ScenarioStep): Promise<Record<str
|
|
|
572
612
|
if (!element)
|
|
573
613
|
return { ok: false, error: 'Element not found', selector: step.selector }
|
|
574
614
|
|
|
615
|
+
if (step.action === 'click' || step.action === 'focus')
|
|
616
|
+
element.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' })
|
|
575
617
|
const rect = element.getBoundingClientRect()
|
|
576
618
|
const style = getComputedStyle(element)
|
|
577
619
|
const visible = rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none'
|
|
@@ -586,6 +628,8 @@ async function runScenarioStep(cdp: Cdp, step: ScenarioStep): Promise<Record<str
|
|
|
586
628
|
if (step.action === 'click') {
|
|
587
629
|
if (element.disabled || element.getAttribute('aria-disabled') === 'true')
|
|
588
630
|
return { ok: false, error: 'Element is disabled', selector: step.selector }
|
|
631
|
+
if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= innerWidth || rect.top >= innerHeight)
|
|
632
|
+
return { ok: false, error: 'Element could not be scrolled into the viewport', selector: step.selector }
|
|
589
633
|
}
|
|
590
634
|
else if (step.action === 'focus') {
|
|
591
635
|
if (typeof element.focus !== 'function')
|
|
@@ -864,7 +908,11 @@ async function main() {
|
|
|
864
908
|
}
|
|
865
909
|
}
|
|
866
910
|
const replacePage = async (): Promise<void> => {
|
|
867
|
-
await closePage(session.port, browserPage)
|
|
911
|
+
const closed = await closePage(session.port, browserPage)
|
|
912
|
+
if (!closed) {
|
|
913
|
+
kill(session)
|
|
914
|
+
session = await launch()
|
|
915
|
+
}
|
|
868
916
|
browserPage = await createReplacementPage()
|
|
869
917
|
cdp = browserPage.cdp
|
|
870
918
|
}
|
|
@@ -1019,6 +1067,16 @@ async function main() {
|
|
|
1019
1067
|
|| page.consoleErrors.length > 0
|
|
1020
1068
|
|| page.failedRequests.length > 0
|
|
1021
1069
|
|| page.horizontalOverflowPx > 0)
|
|
1070
|
+
const summaryOnly = Boolean(flags.summary)
|
|
1071
|
+
const reportedFailures = summaryOnly
|
|
1072
|
+
? failures.map(page => ({
|
|
1073
|
+
path: page.path,
|
|
1074
|
+
status: page.status,
|
|
1075
|
+
consoleErrorCount: page.consoleErrors.length,
|
|
1076
|
+
failedRequestCount: page.failedRequests.length,
|
|
1077
|
+
horizontalOverflowPx: page.horizontalOverflowPx,
|
|
1078
|
+
}))
|
|
1079
|
+
: failures
|
|
1022
1080
|
console.log(JSON.stringify({
|
|
1023
1081
|
start: start.href,
|
|
1024
1082
|
browser: session.browser,
|
|
@@ -1026,8 +1084,9 @@ async function main() {
|
|
|
1026
1084
|
crawled: pages.length,
|
|
1027
1085
|
remaining: queue.length,
|
|
1028
1086
|
max,
|
|
1029
|
-
|
|
1030
|
-
|
|
1087
|
+
failureCount: failures.length,
|
|
1088
|
+
...(!summaryOnly ? { paths: pages.map(page => page.path) } : {}),
|
|
1089
|
+
failures: reportedFailures,
|
|
1031
1090
|
}, null, 2))
|
|
1032
1091
|
if (failures.length > 0)
|
|
1033
1092
|
process.exitCode = 1
|
|
@@ -153,6 +153,9 @@ is hidden. Do not create separate `/deployments/scripts` or
|
|
|
153
153
|
- Use `tag="a"` whenever `href` is reactive, for example
|
|
154
154
|
`<Button tag="a" :href="detailsPath()">Open details</Button>`. Server rendering
|
|
155
155
|
cannot infer an anchor from a client-only reactive URL.
|
|
156
|
+
- When a submit action lives in a shared Modal footer, give the form a stable
|
|
157
|
+
`id` and associate the action with `<Button type="submit" form="form-id">`.
|
|
158
|
+
Do not duplicate the footer inside the form or use script-driven submission.
|
|
156
159
|
- Prefer component events and named slots over string callback props or
|
|
157
160
|
`data-action` markers. A `data-action` attribute is only valid when an active
|
|
158
161
|
host integration consumes that exact action.
|
|
@@ -325,6 +328,7 @@ the canonical primary action style across the dashboard.
|
|
|
325
328
|
</Button>
|
|
326
329
|
|
|
327
330
|
<Button :loading="saving()" type="submit">Save changes</Button>
|
|
331
|
+
<Button :loading="saving()" type="submit" form="settings-form">Save from modal footer</Button>
|
|
328
332
|
<Button variant="secondary" @click="close">Cancel</Button>
|
|
329
333
|
<Button :loading="deleting()" variant="danger" @click="destroy">Delete</Button>
|
|
330
334
|
<Button tag="a" :href="exportHref()" :download="exportFilename()">Export</Button>
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { ask } from '@stacksjs/ai'
|
|
4
|
+
import { log } from '@stacksjs/logging'
|
|
5
|
+
import { response } from '@stacksjs/router'
|
|
3
6
|
import { schema } from '@stacksjs/validation'
|
|
4
7
|
|
|
5
|
-
// TODO: this should have been auto-generated
|
|
6
|
-
interface Request {
|
|
7
|
-
question: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
8
|
export default new Action({
|
|
11
9
|
name: 'AiAskAction',
|
|
12
10
|
description: 'Ask AI',
|
|
13
11
|
method: 'POST',
|
|
12
|
+
apiResponse: true,
|
|
14
13
|
|
|
15
14
|
validations: {
|
|
16
15
|
question: {
|
|
@@ -19,22 +18,18 @@ export default new Action({
|
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
|
|
22
|
-
async handle(request:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log(`Question received: ${question}`)
|
|
21
|
+
async handle(request: RequestInstance) {
|
|
22
|
+
await request.validate()
|
|
23
|
+
const question = String(request.get('question') || '').trim()
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
try {
|
|
26
|
+
return response.json({
|
|
29
27
|
data: await ask(question),
|
|
30
|
-
}
|
|
28
|
+
})
|
|
31
29
|
}
|
|
32
30
|
catch (error) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
error: 'An error occurred while processing your request.',
|
|
37
|
-
}
|
|
31
|
+
log.error('[ai] Ask request failed', { error })
|
|
32
|
+
return response.json({ message: 'The AI provider could not answer the question.' }, 502)
|
|
38
33
|
}
|
|
39
34
|
},
|
|
40
35
|
})
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { summarize } from '@stacksjs/ai'
|
|
4
|
+
import { log } from '@stacksjs/logging'
|
|
5
|
+
import { response } from '@stacksjs/router'
|
|
3
6
|
import { schema } from '@stacksjs/validation'
|
|
4
7
|
|
|
5
|
-
// TODO: this should have been auto-generated
|
|
6
|
-
interface Request {
|
|
7
|
-
text: string
|
|
8
|
-
}
|
|
9
|
-
|
|
10
8
|
export default new Action({
|
|
11
9
|
name: 'AiSummaryAction',
|
|
12
10
|
description: 'Summary AI',
|
|
13
11
|
method: 'POST',
|
|
12
|
+
apiResponse: true,
|
|
14
13
|
|
|
15
14
|
validations: {
|
|
16
15
|
text: {
|
|
@@ -19,22 +18,18 @@ export default new Action({
|
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
|
|
22
|
-
async handle(request:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
console.log(`Text received: ${text}`)
|
|
21
|
+
async handle(request: RequestInstance) {
|
|
22
|
+
await request.validate()
|
|
23
|
+
const text = String(request.get('text') || '').trim()
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
try {
|
|
26
|
+
return response.json({
|
|
29
27
|
data: await summarize(text),
|
|
30
|
-
}
|
|
28
|
+
})
|
|
31
29
|
}
|
|
32
30
|
catch (error) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
error: 'An error occurred while processing your request.',
|
|
37
|
-
}
|
|
31
|
+
log.error('[ai] Summary request failed', { error })
|
|
32
|
+
return response.json({ message: 'The AI provider could not summarize the text.' }, 502)
|
|
38
33
|
}
|
|
39
34
|
},
|
|
40
35
|
})
|
|
@@ -3,7 +3,7 @@ import { Action } from '@stacksjs/actions'
|
|
|
3
3
|
import { response as routerResponse } from '@stacksjs/router'
|
|
4
4
|
import { loadModelIfExists, safeGet } from '../../../../resources/functions/dashboard/data'
|
|
5
5
|
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
|
-
import { isValidModelSlug, type ModelCreateField, type ModelWriteCapabilities, modelCreateFields, modelSchemaColumns, modelWriteCapabilities, slugToPascal } from './model-write'
|
|
6
|
+
import { isValidModelSlug, type ModelCreateField, type ModelWriteCapabilities, modelCreateFields, modelSchemaColumns, modelWritableColumns, modelWriteCapabilities, slugToPascal } from './model-write'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* `GET /api/dashboard/models/{slug}` (stacksjs/stacks#1838).
|
|
@@ -53,6 +53,7 @@ interface ResponseShape {
|
|
|
53
53
|
writable: boolean
|
|
54
54
|
writeCapabilities: ModelWriteCapabilities
|
|
55
55
|
createFields: ModelCreateField[]
|
|
56
|
+
updateColumns: string[]
|
|
56
57
|
error: string | null
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -214,6 +215,7 @@ export default new Action({
|
|
|
214
215
|
writable: false,
|
|
215
216
|
writeCapabilities: { create: false, update: false, destroy: false },
|
|
216
217
|
createFields: [],
|
|
218
|
+
updateColumns: [],
|
|
217
219
|
error: null,
|
|
218
220
|
}
|
|
219
221
|
|
|
@@ -296,6 +298,7 @@ export default new Action({
|
|
|
296
298
|
response.writeCapabilities = modelWriteCapabilities(Model)
|
|
297
299
|
response.writable = Object.values(response.writeCapabilities).some(Boolean)
|
|
298
300
|
response.createFields = response.writeCapabilities.create ? modelCreateFields(Model) : []
|
|
301
|
+
response.updateColumns = response.writeCapabilities.update ? modelWritableColumns(Model) : []
|
|
299
302
|
response.displayColumns = response.columns.filter(col => !col.startsWith('_'))
|
|
300
303
|
response.columnMeta = response.displayColumns.map(name => ({
|
|
301
304
|
name,
|
|
@@ -204,6 +204,11 @@ export function modelCreateFields(Model: any): ModelCreateField[] {
|
|
|
204
204
|
})
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/** Physical database columns accepted by the model's generic update API. */
|
|
208
|
+
export function modelWritableColumns(Model: any): string[] {
|
|
209
|
+
return modelCreateFields(Model).map(field => snakeCase(field.name))
|
|
210
|
+
}
|
|
211
|
+
|
|
207
212
|
function normalizeModelValue(type: ModelCreateField['type'], value: unknown): unknown {
|
|
208
213
|
if (type === 'number') {
|
|
209
214
|
if (value === '' || value === null || value === undefined)
|
package/app/Actions/LogAction.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { log } from '@stacksjs/logging'
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
3
5
|
import { schema } from '@stacksjs/validation'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
message: string
|
|
7
|
-
level: 'info' | 'warn' | 'error'
|
|
8
|
-
}
|
|
7
|
+
type LogLevel = 'info' | 'warn' | 'error'
|
|
9
8
|
|
|
10
9
|
export default new Action({
|
|
11
10
|
name: 'Dummy Logger',
|
|
12
11
|
description: 'This action is used to demo how to POST to a server and upon success, log a message.',
|
|
12
|
+
method: 'POST',
|
|
13
|
+
apiResponse: true,
|
|
13
14
|
|
|
14
15
|
// the request object is optional, but if it is provided, it will be used for validation
|
|
15
16
|
validations: {
|
|
@@ -29,14 +30,13 @@ export default new Action({
|
|
|
29
30
|
},
|
|
30
31
|
},
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!request)
|
|
35
|
-
return 'No request was provided.'
|
|
33
|
+
async handle(request: RequestInstance) {
|
|
34
|
+
await request.validate()
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const message = String(request.get('message') || '').trim()
|
|
37
|
+
const level = String(request.get('level')) as LogLevel
|
|
38
|
+
log[level](message)
|
|
39
39
|
|
|
40
|
-
return `Logged
|
|
40
|
+
return response.json({ level, message: `Logged at ${level} level.` })
|
|
41
41
|
},
|
|
42
42
|
})
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.363",
|
|
6
6
|
"description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"license": "MIT",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script client>
|
|
2
|
+
interface RequestRow {
|
|
3
|
+
id: number
|
|
4
|
+
method: string
|
|
5
|
+
path: string
|
|
6
|
+
status: number
|
|
7
|
+
durationMs: number
|
|
8
|
+
ipAddress: string
|
|
9
|
+
memoryUsageMb: number
|
|
10
|
+
userAgent: string
|
|
11
|
+
errorMessage: string
|
|
12
|
+
createdAt: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const request = useReactiveProp('request', null as RequestRow | null)
|
|
16
|
+
const emit = defineEmits()
|
|
17
|
+
|
|
18
|
+
function formatDate(value: string | undefined): string {
|
|
19
|
+
if (!value)
|
|
20
|
+
return 'Unknown'
|
|
21
|
+
const date = new Date(value)
|
|
22
|
+
return Number.isNaN(date.getTime()) ? 'Unknown' : date.toLocaleString()
|
|
23
|
+
}
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<Modal
|
|
27
|
+
:isOpen="Boolean(request())"
|
|
28
|
+
:title="request() ? 'Request #' + request()?.id : 'Request details'"
|
|
29
|
+
size="lg"
|
|
30
|
+
@close="emit('close')"
|
|
31
|
+
>
|
|
32
|
+
<p class="mb-5 break-all font-mono text-neutral-500 text-sm dark:text-neutral-400">{{ request()?.method }} {{ request()?.path }}</p>
|
|
33
|
+
<dl class="grid gap-4 grid-cols-1 sm:grid-cols-2 text-sm">
|
|
34
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Status</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.status }}</dd></div>
|
|
35
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Duration</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.durationMs?.toFixed(1) || '0.0' }} ms</dd></div>
|
|
36
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">IP address</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.ipAddress || 'Not recorded' }}</dd></div>
|
|
37
|
+
<div><dt class="text-neutral-500 dark:text-neutral-400">Memory usage</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ request()?.memoryUsageMb || 0 }} MB</dd></div>
|
|
38
|
+
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">Recorded</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ formatDate(request()?.createdAt) }}</dd></div>
|
|
39
|
+
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">User agent</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ request()?.userAgent || 'Not recorded' }}</dd></div>
|
|
40
|
+
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">Error</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ request()?.errorMessage || 'No error recorded' }}</dd></div>
|
|
41
|
+
</dl>
|
|
42
|
+
</Modal>
|
|
@@ -229,25 +229,5 @@ onMount(() => {
|
|
|
229
229
|
</div>
|
|
230
230
|
</Card>
|
|
231
231
|
|
|
232
|
-
<
|
|
233
|
-
<div role="dialog" aria-modal="true" aria-labelledby="request-detail-title" class="p-6 max-w-2xl w-full bg-white dark:bg-neutral-900 border border-neutral-200 rounded-xl dark:border-neutral-700 shadow-xl">
|
|
234
|
-
<div class="flex gap-4 items-start justify-between">
|
|
235
|
-
<div>
|
|
236
|
-
<h2 id="request-detail-title" class="font-semibold text-lg text-neutral-900 dark:text-white">Request #{{ selectedRequest()?.id }}</h2>
|
|
237
|
-
<p class="mt-1 break-all font-mono text-neutral-500 text-sm dark:text-neutral-400">{{ selectedRequest()?.method }} {{ selectedRequest()?.path }}</p>
|
|
238
|
-
</div>
|
|
239
|
-
<Button variant="ghost" iconOnly ariaLabel="Close request details" @click="closeInspector">
|
|
240
|
-
<span class="h-5 w-5 i-hugeicons-cancel-01"></span>
|
|
241
|
-
</Button>
|
|
242
|
-
</div>
|
|
243
|
-
<dl class="grid gap-4 grid-cols-1 sm:grid-cols-2 mt-6 text-sm">
|
|
244
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Status</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.status }}</dd></div>
|
|
245
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Duration</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.durationMs.toFixed(1) }} ms</dd></div>
|
|
246
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">IP address</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.ipAddress || 'Not recorded' }}</dd></div>
|
|
247
|
-
<div><dt class="text-neutral-500 dark:text-neutral-400">Memory usage</dt><dd class="mt-1 font-medium text-neutral-900 dark:text-white">{{ selectedRequest()?.memoryUsageMb || 0 }} MB</dd></div>
|
|
248
|
-
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">User agent</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ selectedRequest()?.userAgent || 'Not recorded' }}</dd></div>
|
|
249
|
-
<div class="sm:col-span-2"><dt class="text-neutral-500 dark:text-neutral-400">Error</dt><dd class="mt-1 break-words text-neutral-900 dark:text-white">{{ selectedRequest()?.errorMessage || 'No error recorded' }}</dd></div>
|
|
250
|
-
</dl>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
232
|
+
<RequestDetailsDialog :request="selectedRequest()" @close="closeInspector" />
|
|
253
233
|
</div>
|