@askrjs/cli 0.0.11 → 0.0.12
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 +4 -2
- package/dist/analyze.js +1 -1
- package/dist/cli.js +1 -1
- package/dist/{guardrails-vH9lYg05.js → guardrails-GPM9PJqO.js} +1 -1
- package/dist/{runner-42nqhW9u.js → runner-BRjjPhZY.js} +56 -13
- package/dist/{runner-tPGn2CQC.js → runner-Ca43qqzG.js} +1 -1
- package/dist/templates/spa/src/pages/app/_layout.tsx +11 -8
- package/dist/templates/spa/src/pages/app/admin-home.tsx +97 -90
- package/dist/templates/spa/src/pages/app/agent-runs.tsx +33 -26
- package/dist/templates/spa/src/pages/public/home.tsx +12 -9
- package/dist/templates/ssg/src/components/site-shell.tsx +4 -3
- package/dist/templates/ssg/src/pages/about.tsx +14 -11
- package/dist/templates/ssg/src/pages/content.tsx +10 -7
- package/dist/templates/ssg/src/pages/home.tsx +6 -3
- package/dist/templates/startkit/src/components/app-sidebar.tsx +23 -18
- package/dist/templates/startkit/src/components/data-table.tsx +8 -6
- package/dist/templates/startkit/src/pages/workspace/dashboard.tsx +16 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,8 +73,10 @@ askr analyze --json --check
|
|
|
73
73
|
|
|
74
74
|
All diagnostics include a stable rule ID and workspace-relative source
|
|
75
75
|
location. The analyzer distinguishes canonical Askr imports from unrelated
|
|
76
|
-
same-named functions and
|
|
77
|
-
|
|
76
|
+
same-named functions and recommends `<For>` only when a `.map()` result is
|
|
77
|
+
rendered directly as JSX children, so ordinary data transforms remain valid.
|
|
78
|
+
It reports eager `<For>`/`<Show>`/`<Case>` controls behind changing ternaries
|
|
79
|
+
while accepting conditionally mounted components with their own render scope.
|
|
78
80
|
|
|
79
81
|
By default it transactionally applies only mechanical route-parameter and
|
|
80
82
|
plain-JSON JSX configuration fixes. `--check` is read-only for CI. Semantic
|
package/dist/analyze.js
CHANGED
|
@@ -71,7 +71,7 @@ async function runAnalyzeCli(args = process.argv.slice(2), io = console, runtime
|
|
|
71
71
|
io.log(helpText.trimEnd());
|
|
72
72
|
return 0;
|
|
73
73
|
}
|
|
74
|
-
const report = await (runtime.analyze ?? (await import("./runner-
|
|
74
|
+
const report = await (runtime.analyze ?? (await import("./runner-BRjjPhZY.js")).runAnalysis)({
|
|
75
75
|
cwd: parsed.cwd,
|
|
76
76
|
workspacePatterns: parsed.workspacePatterns,
|
|
77
77
|
check: parsed.check
|
package/dist/cli.js
CHANGED
|
@@ -90,7 +90,7 @@ async function runCli(args = process.argv.slice(2), io = console) {
|
|
|
90
90
|
return runAnalyzeCli(args.slice(1), io);
|
|
91
91
|
}
|
|
92
92
|
if (command === "check" || command === "doctor" || command === "repair") {
|
|
93
|
-
const { runGuardrailCli } = await import("./guardrails-
|
|
93
|
+
const { runGuardrailCli } = await import("./guardrails-GPM9PJqO.js");
|
|
94
94
|
return runGuardrailCli(command, args.slice(1), io);
|
|
95
95
|
}
|
|
96
96
|
if (command === "generate") {
|
|
@@ -98,7 +98,7 @@ async function runGuardrailCli(command, args = process.argv.slice(2), io = conso
|
|
|
98
98
|
cwd: parsed.cwd,
|
|
99
99
|
workspacePatterns: parsed.workspacePatterns
|
|
100
100
|
};
|
|
101
|
-
const { runCheck, runDoctor, runRepair } = await import("./runner-
|
|
101
|
+
const { runCheck, runDoctor, runRepair } = await import("./runner-Ca43qqzG.js");
|
|
102
102
|
const report = command === "doctor" ? await runDoctor(options, runtime) : command === "repair" ? await runRepair(options) : await runCheck(options, runtime);
|
|
103
103
|
if (parsed.json) io.log(JSON.stringify(report));
|
|
104
104
|
else if (report.command === "doctor") printDoctor(report, io);
|
|
@@ -354,6 +354,30 @@ function functionName(node) {
|
|
|
354
354
|
}
|
|
355
355
|
return null;
|
|
356
356
|
}
|
|
357
|
+
const EAGER_CONTROL_CONCEPTS = /* @__PURE__ */ new Set([
|
|
358
|
+
"Case",
|
|
359
|
+
"For",
|
|
360
|
+
"Show"
|
|
361
|
+
]);
|
|
362
|
+
function isConstantCondition(expression) {
|
|
363
|
+
if (ts.isParenthesizedExpression(expression)) return isConstantCondition(expression.expression);
|
|
364
|
+
return expression.kind === ts.SyntaxKind.TrueKeyword || expression.kind === ts.SyntaxKind.FalseKeyword || expression.kind === ts.SyntaxKind.NullKeyword || ts.isNumericLiteral(expression) || ts.isStringLiteral(expression) || ts.isNoSubstitutionTemplateLiteral(expression);
|
|
365
|
+
}
|
|
366
|
+
function unstableControlConditional(node, bindings) {
|
|
367
|
+
const ownElement = ts.isJsxOpeningElement(node) && ts.isJsxElement(node.parent) ? node.parent : null;
|
|
368
|
+
let child = node;
|
|
369
|
+
for (let current = node.parent; current; current = current.parent) {
|
|
370
|
+
if (ts.isFunctionLike(current)) return null;
|
|
371
|
+
if (current !== ownElement && ts.isJsxElement(current)) {
|
|
372
|
+
const name = canonicalJsxName(current.openingElement.tagName, bindings);
|
|
373
|
+
if (name && EAGER_CONTROL_CONCEPTS.has(name)) return null;
|
|
374
|
+
}
|
|
375
|
+
if (ts.isConditionalExpression(current) && (current.whenTrue === child || current.whenFalse === child) && !isConstantCondition(current.condition)) return current;
|
|
376
|
+
if (ts.isBinaryExpression(current) && [ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken].includes(current.operatorToken.kind) && current.right === child && !isConstantCondition(current.left)) return current;
|
|
377
|
+
child = current;
|
|
378
|
+
}
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
357
381
|
const stableRenderRule = {
|
|
358
382
|
id: "askr/stable-render-call",
|
|
359
383
|
category: "correctness",
|
|
@@ -375,6 +399,14 @@ const stableRenderRule = {
|
|
|
375
399
|
}
|
|
376
400
|
if (isControlFlowAncestor(node, owner) && (!POSITIONAL_DATA_CONCEPTS.has(name) || /^[A-Z]/.test(functionName(owner) ?? "") || containsJsx(owner))) diagnostics.push(diagnostic(context, node.expression, this, `${name}() is called conditionally, so its render position is unstable.`, `Call ${name}() unconditionally at the top level and branch on its result.`));
|
|
377
401
|
});
|
|
402
|
+
visit(sourceFile, (node) => {
|
|
403
|
+
if (!ts.isJsxOpeningElement(node) && !ts.isJsxSelfClosingElement(node)) return;
|
|
404
|
+
const name = canonicalJsxName(node.tagName, bindings);
|
|
405
|
+
if (!name || !EAGER_CONTROL_CONCEPTS.has(name)) return;
|
|
406
|
+
if (!unstableControlConditional(node, bindings)) return;
|
|
407
|
+
const remediation = name === "Show" ? "Mount <Show> unconditionally and move the condition into its when prop." : "Replace the ternary or logical expression with a <Show> boundary.";
|
|
408
|
+
diagnostics.push(diagnostic(context, node.tagName, this, `<${name}> is mounted behind a changing conditional, so its render position is unstable.`, remediation));
|
|
409
|
+
});
|
|
378
410
|
}
|
|
379
411
|
return diagnostics;
|
|
380
412
|
}
|
|
@@ -594,26 +626,37 @@ const stableKeyRule = {
|
|
|
594
626
|
return diagnostics;
|
|
595
627
|
}
|
|
596
628
|
};
|
|
597
|
-
function
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
629
|
+
function mapResultIsJsxChild(node) {
|
|
630
|
+
let current = node;
|
|
631
|
+
for (;;) {
|
|
632
|
+
const parent = current.parent;
|
|
633
|
+
if (ts.isJsxExpression(parent)) return !ts.isJsxAttribute(parent.parent);
|
|
634
|
+
if ((ts.isParenthesizedExpression(parent) || ts.isAsExpression(parent) || ts.isTypeAssertionExpression(parent) || ts.isNonNullExpression(parent) || ts.isSatisfiesExpression(parent)) && parent.expression === current) {
|
|
635
|
+
current = parent;
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
if (ts.isConditionalExpression(parent) && (parent.whenTrue === current || parent.whenFalse === current)) {
|
|
639
|
+
current = parent;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
if (ts.isBinaryExpression(parent) && (parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken && parent.right === current || parent.operatorToken.kind === ts.SyntaxKind.BarBarToken && (parent.left === current || parent.right === current) || parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken && (parent.left === current || parent.right === current))) {
|
|
643
|
+
current = parent;
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
return false;
|
|
647
|
+
}
|
|
602
648
|
}
|
|
603
649
|
const preferForRule = {
|
|
604
650
|
id: "askr/prefer-for",
|
|
605
651
|
category: "performance",
|
|
606
652
|
severity: "warning",
|
|
607
|
-
description: "
|
|
653
|
+
description: "Collection arrays rendered as JSX children should use For.",
|
|
608
654
|
analyze(context) {
|
|
609
655
|
const diagnostics = [];
|
|
610
|
-
for (const sourceFile of context.sourceFiles) {
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
diagnostics.push(diagnostic(context, node.expression.name, this, "A reactive collection is rendered with .map(), bypassing keyed <For> reconciliation.", "Render it with <For each={...} by={...}>. This semantic rewrite is report-only."));
|
|
615
|
-
});
|
|
616
|
-
}
|
|
656
|
+
for (const sourceFile of context.sourceFiles) visit(sourceFile, (node) => {
|
|
657
|
+
if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression) || node.expression.name.text !== "map" || !mapResultIsJsxChild(node)) return;
|
|
658
|
+
diagnostics.push(diagnostic(context, node.expression.name, this, "A collection array is rendered with .map(), bypassing keyed <For> reconciliation.", "Render it with <For each={...} by={...}>. This semantic rewrite is report-only."));
|
|
659
|
+
});
|
|
617
660
|
return diagnostics;
|
|
618
661
|
}
|
|
619
662
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as inspectBundledSkills } from "./skills-CSGdAZHN.js";
|
|
2
2
|
import { discoverWorkspaceProject } from "./discovery-DUDrZCIC.js";
|
|
3
|
-
import { analysisHasBlockingFindings, runAnalysis } from "./runner-
|
|
3
|
+
import { analysisHasBlockingFindings, runAnalysis } from "./runner-BRjjPhZY.js";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SettingsIcon,
|
|
7
7
|
SunIcon,
|
|
8
8
|
} from '@askrjs/lucide';
|
|
9
|
+
import { For } from '@askrjs/askr/control';
|
|
9
10
|
import { Link, navigate } from '@askrjs/askr/router';
|
|
10
11
|
import { Button } from '@askrjs/themes/components';
|
|
11
12
|
import { Container, Inline, Stack } from '@askrjs/themes/components';
|
|
@@ -42,14 +43,16 @@ export default function AppLayout({ children }: { children?: unknown }) {
|
|
|
42
43
|
</Link>
|
|
43
44
|
</NavBrand>
|
|
44
45
|
<NavGroup label="Workspace">
|
|
45
|
-
{appNavItems
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
<For each={[...appNavItems]} by={(item) => item.href}>
|
|
47
|
+
{(item) => (
|
|
48
|
+
<NavLink href={item.href} match={item.match}>
|
|
49
|
+
<Inline as="span" gap="2" align="center">
|
|
50
|
+
{icons[item.icon]}
|
|
51
|
+
<span>{item.label}</span>
|
|
52
|
+
</Inline>
|
|
53
|
+
</NavLink>
|
|
54
|
+
)}
|
|
55
|
+
</For>
|
|
53
56
|
</NavGroup>
|
|
54
57
|
<NavGroup label="Session" align="end">
|
|
55
58
|
<NavLink href="/" match="exact">
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resource } from '@askrjs/askr/resources';
|
|
2
|
+
import { For, Show } from '@askrjs/askr/control';
|
|
2
3
|
import { createPlot } from '@askrjs/charts';
|
|
3
4
|
import { AlertCircleIcon, RefreshCwIcon } from '@askrjs/lucide';
|
|
4
5
|
import { Button } from '@askrjs/themes/components';
|
|
@@ -66,105 +67,111 @@ export default function AdminHomePage() {
|
|
|
66
67
|
</Block>
|
|
67
68
|
) : null}
|
|
68
69
|
|
|
69
|
-
{snapshot
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
70
|
+
<Show when={snapshot}>
|
|
71
|
+
{(currentSnapshot) => (
|
|
72
|
+
<>
|
|
73
|
+
<Block gap="md" class="metric-grid">
|
|
74
|
+
<For each={currentSnapshot.metrics} by={(metric) => metric.label}>
|
|
75
|
+
{(metric) => (
|
|
76
|
+
<MetricCard
|
|
77
|
+
label={metric.label}
|
|
78
|
+
value={metric.value}
|
|
79
|
+
trend={metric.trend}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
</For>
|
|
83
|
+
</Block>
|
|
84
|
+
|
|
85
|
+
<Block gap="md" align="stretch" class="chart-grid">
|
|
86
|
+
<Card>
|
|
87
|
+
<CardHeader>
|
|
88
|
+
<CardTitle>Run throughput</CardTitle>
|
|
89
|
+
<CardDescription>
|
|
90
|
+
Accepted commands by work type.
|
|
91
|
+
</CardDescription>
|
|
92
|
+
</CardHeader>
|
|
93
|
+
<CardContent>
|
|
94
|
+
<OperationsPlot.Root
|
|
95
|
+
data={currentSnapshot.throughput}
|
|
96
|
+
rowKey="label"
|
|
97
|
+
label="Run throughput"
|
|
98
|
+
description="Accepted commands by work type."
|
|
99
|
+
>
|
|
100
|
+
<OperationsPlot.Bar x="label" y="value" />
|
|
101
|
+
</OperationsPlot.Root>
|
|
102
|
+
</CardContent>
|
|
103
|
+
</Card>
|
|
104
|
+
<Card>
|
|
105
|
+
<CardHeader>
|
|
106
|
+
<CardTitle>Projection lag</CardTitle>
|
|
107
|
+
<CardDescription>
|
|
108
|
+
Lower is better; stale states stay visible.
|
|
109
|
+
</CardDescription>
|
|
110
|
+
</CardHeader>
|
|
111
|
+
<CardContent>
|
|
112
|
+
<OperationsPlot.Root
|
|
113
|
+
data={currentSnapshot.lag}
|
|
114
|
+
rowKey="label"
|
|
115
|
+
label="Projection lag"
|
|
116
|
+
description="Projection lag over the last hour."
|
|
117
|
+
>
|
|
118
|
+
<OperationsPlot.Line x="label" y="value" />
|
|
119
|
+
<OperationsPlot.Point x="label" y="value" />
|
|
120
|
+
</OperationsPlot.Root>
|
|
121
|
+
</CardContent>
|
|
122
|
+
</Card>
|
|
123
|
+
</Block>
|
|
124
|
+
|
|
125
|
+
{currentSnapshot.consistency !== 'fresh' ? (
|
|
126
|
+
<Alert variant="warning">
|
|
127
|
+
Read models are {currentSnapshot.consistency}. Last processed
|
|
128
|
+
event is {currentSnapshot.lastEventId}.
|
|
129
|
+
</Alert>
|
|
130
|
+
) : null}
|
|
80
131
|
|
|
81
|
-
<Block gap="md" align="stretch" class="chart-grid">
|
|
82
|
-
<Card>
|
|
83
|
-
<CardHeader>
|
|
84
|
-
<CardTitle>Run throughput</CardTitle>
|
|
85
|
-
<CardDescription>
|
|
86
|
-
Accepted commands by work type.
|
|
87
|
-
</CardDescription>
|
|
88
|
-
</CardHeader>
|
|
89
|
-
<CardContent>
|
|
90
|
-
<OperationsPlot.Root
|
|
91
|
-
data={snapshot.throughput}
|
|
92
|
-
rowKey="label"
|
|
93
|
-
label="Run throughput"
|
|
94
|
-
description="Accepted commands by work type."
|
|
95
|
-
>
|
|
96
|
-
<OperationsPlot.Bar x="label" y="value" />
|
|
97
|
-
</OperationsPlot.Root>
|
|
98
|
-
</CardContent>
|
|
99
|
-
</Card>
|
|
100
132
|
<Card>
|
|
101
133
|
<CardHeader>
|
|
102
|
-
<CardTitle>
|
|
134
|
+
<CardTitle>Recent agent runs</CardTitle>
|
|
103
135
|
<CardDescription>
|
|
104
|
-
|
|
136
|
+
Run state is modeled as product state, not a single loading
|
|
137
|
+
boolean.
|
|
105
138
|
</CardDescription>
|
|
106
139
|
</CardHeader>
|
|
107
140
|
<CardContent>
|
|
108
|
-
<
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
label="Projection lag"
|
|
112
|
-
description="Projection lag over the last hour."
|
|
113
|
-
>
|
|
114
|
-
<OperationsPlot.Line x="label" y="value" />
|
|
115
|
-
<OperationsPlot.Point x="label" y="value" />
|
|
116
|
-
</OperationsPlot.Root>
|
|
117
|
-
</CardContent>
|
|
118
|
-
</Card>
|
|
119
|
-
</Block>
|
|
120
|
-
|
|
121
|
-
{snapshot.consistency !== 'fresh' ? (
|
|
122
|
-
<Alert variant="warning">
|
|
123
|
-
Read models are {snapshot.consistency}. Last processed event is{' '}
|
|
124
|
-
{snapshot.lastEventId}.
|
|
125
|
-
</Alert>
|
|
126
|
-
) : null}
|
|
127
|
-
|
|
128
|
-
<Card>
|
|
129
|
-
<CardHeader>
|
|
130
|
-
<CardTitle>Recent agent runs</CardTitle>
|
|
131
|
-
<CardDescription>
|
|
132
|
-
Run state is modeled as product state, not a single loading
|
|
133
|
-
boolean.
|
|
134
|
-
</CardDescription>
|
|
135
|
-
</CardHeader>
|
|
136
|
-
<CardContent>
|
|
137
|
-
<div class="run-table-wrap">
|
|
138
|
-
<table class="run-table">
|
|
139
|
-
<thead>
|
|
140
|
-
<tr>
|
|
141
|
-
<th>Run</th>
|
|
142
|
-
<th>Status</th>
|
|
143
|
-
<th>Owner</th>
|
|
144
|
-
<th>Updated</th>
|
|
145
|
-
</tr>
|
|
146
|
-
</thead>
|
|
147
|
-
<tbody>
|
|
148
|
-
{snapshot.runs.map((run) => (
|
|
141
|
+
<div class="run-table-wrap">
|
|
142
|
+
<table class="run-table">
|
|
143
|
+
<thead>
|
|
149
144
|
<tr>
|
|
150
|
-
<
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
</
|
|
154
|
-
<td>
|
|
155
|
-
<StatusBadge status={run.status} />
|
|
156
|
-
</td>
|
|
157
|
-
<td>{run.owner}</td>
|
|
158
|
-
<td>{formatRelativeTime(run.updatedAt)}</td>
|
|
145
|
+
<th>Run</th>
|
|
146
|
+
<th>Status</th>
|
|
147
|
+
<th>Owner</th>
|
|
148
|
+
<th>Updated</th>
|
|
159
149
|
</tr>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
150
|
+
</thead>
|
|
151
|
+
<tbody>
|
|
152
|
+
<For each={currentSnapshot.runs} by={(run) => run.id}>
|
|
153
|
+
{(run) => (
|
|
154
|
+
<tr>
|
|
155
|
+
<td>
|
|
156
|
+
<strong>{run.title}</strong>
|
|
157
|
+
<span>{run.id}</span>
|
|
158
|
+
</td>
|
|
159
|
+
<td>
|
|
160
|
+
<StatusBadge status={run.status} />
|
|
161
|
+
</td>
|
|
162
|
+
<td>{run.owner}</td>
|
|
163
|
+
<td>{formatRelativeTime(run.updatedAt)}</td>
|
|
164
|
+
</tr>
|
|
165
|
+
)}
|
|
166
|
+
</For>
|
|
167
|
+
</tbody>
|
|
168
|
+
</table>
|
|
169
|
+
</div>
|
|
170
|
+
</CardContent>
|
|
171
|
+
</Card>
|
|
172
|
+
</>
|
|
173
|
+
)}
|
|
174
|
+
</Show>
|
|
168
175
|
</Stack>
|
|
169
176
|
);
|
|
170
177
|
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Clock3Icon,
|
|
5
5
|
ShieldAlertIcon,
|
|
6
6
|
} from '@askrjs/lucide';
|
|
7
|
+
import { For } from '@askrjs/askr/control';
|
|
7
8
|
import {
|
|
8
9
|
Badge,
|
|
9
10
|
Card,
|
|
@@ -18,12 +19,14 @@ import StatusBadge, {
|
|
|
18
19
|
} from '../../components/shared/status-badge';
|
|
19
20
|
|
|
20
21
|
const runs: Array<{
|
|
22
|
+
id: string;
|
|
21
23
|
title: string;
|
|
22
24
|
status: RunStatus;
|
|
23
25
|
event: string;
|
|
24
26
|
description: string;
|
|
25
27
|
}> = [
|
|
26
28
|
{
|
|
29
|
+
id: 'reconcile-billing-projection',
|
|
27
30
|
title: 'Reconcile billing projection',
|
|
28
31
|
status: 'running',
|
|
29
32
|
event: 'tool call: compare-ledger',
|
|
@@ -31,6 +34,7 @@ const runs: Array<{
|
|
|
31
34
|
'Streaming events are appended to the timeline and reconciled by event id.',
|
|
32
35
|
},
|
|
33
36
|
{
|
|
37
|
+
id: 'approve-enterprise-workspace',
|
|
34
38
|
title: 'Approve enterprise workspace',
|
|
35
39
|
status: 'requires-action',
|
|
36
40
|
event: 'approval requested',
|
|
@@ -38,6 +42,7 @@ const runs: Array<{
|
|
|
38
42
|
'Human gates are explicit product states, not hidden inside generated text.',
|
|
39
43
|
},
|
|
40
44
|
{
|
|
45
|
+
id: 'refresh-onboarding-cohort',
|
|
41
46
|
title: 'Refresh onboarding cohort',
|
|
42
47
|
status: 'succeeded',
|
|
43
48
|
event: 'projection caught up',
|
|
@@ -61,32 +66,34 @@ export default function AgentRunsPage() {
|
|
|
61
66
|
</section>
|
|
62
67
|
|
|
63
68
|
<Block gap="md" class="agent-grid">
|
|
64
|
-
{runs
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
<
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
<
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
69
|
+
<For each={runs} by={(run) => run.id}>
|
|
70
|
+
{(run) => (
|
|
71
|
+
<Card>
|
|
72
|
+
<CardHeader>
|
|
73
|
+
<Inline justify="between" align="start" gap="3">
|
|
74
|
+
<span class="card-icon">
|
|
75
|
+
{run.status === 'succeeded' ? (
|
|
76
|
+
<CheckCircle2Icon size={18} aria-hidden="true" />
|
|
77
|
+
) : run.status === 'requires-action' ? (
|
|
78
|
+
<ShieldAlertIcon size={18} aria-hidden="true" />
|
|
79
|
+
) : (
|
|
80
|
+
<BotIcon size={18} aria-hidden="true" />
|
|
81
|
+
)}
|
|
82
|
+
</span>
|
|
83
|
+
<StatusBadge status={run.status} />
|
|
84
|
+
</Inline>
|
|
85
|
+
<CardTitle>{run.title}</CardTitle>
|
|
86
|
+
<CardDescription>{run.description}</CardDescription>
|
|
87
|
+
</CardHeader>
|
|
88
|
+
<CardContent>
|
|
89
|
+
<Inline gap="2" align="center">
|
|
90
|
+
<Clock3Icon size={14} aria-hidden="true" />
|
|
91
|
+
<span>{run.event}</span>
|
|
92
|
+
</Inline>
|
|
93
|
+
</CardContent>
|
|
94
|
+
</Card>
|
|
95
|
+
)}
|
|
96
|
+
</For>
|
|
90
97
|
</Block>
|
|
91
98
|
</Stack>
|
|
92
99
|
);
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
CheckCircle2Icon,
|
|
6
6
|
ShieldCheckIcon,
|
|
7
7
|
} from '@askrjs/lucide';
|
|
8
|
+
import { For } from '@askrjs/askr/control';
|
|
8
9
|
import { Link } from '@askrjs/askr/router';
|
|
9
10
|
import { Button } from '@askrjs/themes/components';
|
|
10
11
|
import {
|
|
@@ -111,15 +112,17 @@ export default function HomePage() {
|
|
|
111
112
|
<Section paddingY="xl">
|
|
112
113
|
<Container size="xl">
|
|
113
114
|
<Block gap="md" class="feature-grid">
|
|
114
|
-
{capabilities
|
|
115
|
-
|
|
116
|
-
<
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
115
|
+
<For each={capabilities} by={(item) => item.title}>
|
|
116
|
+
{(item) => (
|
|
117
|
+
<Card>
|
|
118
|
+
<CardHeader>
|
|
119
|
+
<span class="card-icon">{item.icon}</span>
|
|
120
|
+
<CardTitle>{item.title}</CardTitle>
|
|
121
|
+
<CardDescription>{item.description}</CardDescription>
|
|
122
|
+
</CardHeader>
|
|
123
|
+
</Card>
|
|
124
|
+
)}
|
|
125
|
+
</For>
|
|
123
126
|
</Block>
|
|
124
127
|
</Container>
|
|
125
128
|
</Section>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { For } from '@askrjs/askr/control';
|
|
1
2
|
import { Link } from '@askrjs/askr/router';
|
|
2
3
|
import { Header } from '@askrjs/themes/components';
|
|
3
4
|
import {
|
|
@@ -35,9 +36,9 @@ export function SiteHeader() {
|
|
|
35
36
|
class="navbar-group"
|
|
36
37
|
data-align="end"
|
|
37
38
|
>
|
|
38
|
-
{navItems
|
|
39
|
-
<NavLink href={item.href}>{item.label}</NavLink>
|
|
40
|
-
|
|
39
|
+
<For each={[...navItems]} by={(item) => item.href}>
|
|
40
|
+
{(item) => <NavLink href={item.href}>{item.label}</NavLink>}
|
|
41
|
+
</For>
|
|
41
42
|
</Nav>
|
|
42
43
|
</Box>
|
|
43
44
|
</Container>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { For } from '@askrjs/askr/control';
|
|
1
2
|
import { Link } from '@askrjs/askr/router';
|
|
2
3
|
import { Button } from '@askrjs/ui';
|
|
3
4
|
import {
|
|
@@ -54,17 +55,19 @@ export default function Workflow() {
|
|
|
54
55
|
/>
|
|
55
56
|
|
|
56
57
|
<CardGrid>
|
|
57
|
-
{steps
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
<For each={steps} by={(step) => step.number}>
|
|
59
|
+
{(step) => (
|
|
60
|
+
<Card
|
|
61
|
+
eyebrow={step.number}
|
|
62
|
+
title={step.title}
|
|
63
|
+
description={step.body}
|
|
64
|
+
>
|
|
65
|
+
<p>
|
|
66
|
+
<code>{step.command}</code>
|
|
67
|
+
</p>
|
|
68
|
+
</Card>
|
|
69
|
+
)}
|
|
70
|
+
</For>
|
|
68
71
|
</CardGrid>
|
|
69
72
|
|
|
70
73
|
<Card
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { For } from '@askrjs/askr/control';
|
|
1
2
|
import { Link } from '@askrjs/askr/router';
|
|
2
3
|
import { Button } from '@askrjs/ui';
|
|
3
4
|
import {
|
|
@@ -50,13 +51,15 @@ export default function Content() {
|
|
|
50
51
|
/>
|
|
51
52
|
|
|
52
53
|
<CardGrid>
|
|
53
|
-
{routeMap
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
<For each={[...routeMap]} by={(route) => route.path}>
|
|
55
|
+
{(route) => (
|
|
56
|
+
<Card
|
|
57
|
+
eyebrow={route.path}
|
|
58
|
+
title={route.title}
|
|
59
|
+
description={route.note}
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
</For>
|
|
60
63
|
</CardGrid>
|
|
61
64
|
</>
|
|
62
65
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { For } from '@askrjs/askr/control';
|
|
1
2
|
import { Link } from '@askrjs/askr/router';
|
|
2
3
|
import { Button } from '@askrjs/ui';
|
|
3
4
|
import {
|
|
@@ -42,9 +43,11 @@ export default function Home() {
|
|
|
42
43
|
/>
|
|
43
44
|
|
|
44
45
|
<CardGrid>
|
|
45
|
-
{highlights
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
<For each={highlights} by={(highlight) => highlight.title}>
|
|
47
|
+
{(highlight) => (
|
|
48
|
+
<Card title={highlight.title} description={highlight.body} />
|
|
49
|
+
)}
|
|
50
|
+
</For>
|
|
48
51
|
</CardGrid>
|
|
49
52
|
</>
|
|
50
53
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { For } from '@askrjs/askr/control';
|
|
1
2
|
import { Link } from '@askrjs/askr/router';
|
|
2
3
|
import {
|
|
3
4
|
LayoutDashboardIcon,
|
|
@@ -67,27 +68,31 @@ export default function AppSidebar() {
|
|
|
67
68
|
</NavBrand>
|
|
68
69
|
|
|
69
70
|
<NavGroup id="workspace-nav-group" label="Workspace">
|
|
70
|
-
{primaryNav
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
71
|
+
<For each={primaryNav} by={(item) => item.href}>
|
|
72
|
+
{(item) => {
|
|
73
|
+
const Icon = item.icon;
|
|
74
|
+
return (
|
|
75
|
+
<NavLink href={item.href}>
|
|
76
|
+
<Icon size={16} aria-hidden={true} />
|
|
77
|
+
<span>{item.label}</span>
|
|
78
|
+
</NavLink>
|
|
79
|
+
);
|
|
80
|
+
}}
|
|
81
|
+
</For>
|
|
79
82
|
</NavGroup>
|
|
80
83
|
|
|
81
84
|
<NavGroup id="other-nav-group" label="Other" placement="bottom">
|
|
82
|
-
{secondaryNav
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
<For each={secondaryNav} by={(item) => item.href}>
|
|
86
|
+
{(item) => {
|
|
87
|
+
const Icon = item.icon;
|
|
88
|
+
return (
|
|
89
|
+
<NavLink href={item.href}>
|
|
90
|
+
<Icon size={16} aria-hidden={true} />
|
|
91
|
+
<span>{item.label}</span>
|
|
92
|
+
</NavLink>
|
|
93
|
+
);
|
|
94
|
+
}}
|
|
95
|
+
</For>
|
|
91
96
|
</NavGroup>
|
|
92
97
|
</Navbar>
|
|
93
98
|
</aside>
|
|
@@ -58,18 +58,20 @@ export default function DataTable<Row>(props: {
|
|
|
58
58
|
<table class={props.tableClass}>
|
|
59
59
|
<thead>
|
|
60
60
|
<tr>
|
|
61
|
-
{props.columns
|
|
62
|
-
<th class={column.class}>{column.header}</th>
|
|
63
|
-
|
|
61
|
+
<For each={props.columns} by={(column) => column.key}>
|
|
62
|
+
{(column) => <th class={column.class}>{column.header}</th>}
|
|
63
|
+
</For>
|
|
64
64
|
</tr>
|
|
65
65
|
</thead>
|
|
66
66
|
<tbody>
|
|
67
67
|
<For each={props.rows} by={props.rowKey}>
|
|
68
68
|
{(row: Row) => (
|
|
69
69
|
<tr class={props.rowClass?.(row)}>
|
|
70
|
-
{props.columns
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
<For each={props.columns} by={(column) => column.key}>
|
|
71
|
+
{(column) => (
|
|
72
|
+
<td class={column.class}>{column.render(row)}</td>
|
|
73
|
+
)}
|
|
74
|
+
</For>
|
|
73
75
|
</tr>
|
|
74
76
|
)}
|
|
75
77
|
</For>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { state } from '@askrjs/askr';
|
|
2
|
+
import { For } from '@askrjs/askr/control';
|
|
2
3
|
import { resource } from '@askrjs/askr/resources';
|
|
3
4
|
import { Button } from '@askrjs/ui/button';
|
|
4
5
|
import {
|
|
@@ -61,19 +62,21 @@ export default function DashboardPage() {
|
|
|
61
62
|
/>
|
|
62
63
|
|
|
63
64
|
<div class="stat-grid">
|
|
64
|
-
{stats(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
65
|
+
<For each={stats} by={(stat) => stat.key}>
|
|
66
|
+
{(stat) => {
|
|
67
|
+
const Icon =
|
|
68
|
+
iconByStatKey[stat.key as keyof typeof iconByStatKey] ??
|
|
69
|
+
BarChart3Icon;
|
|
70
|
+
return (
|
|
71
|
+
<StatCard
|
|
72
|
+
label={stat.label}
|
|
73
|
+
value={stat.value}
|
|
74
|
+
trend={stat.trend}
|
|
75
|
+
icon={Icon}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}}
|
|
79
|
+
</For>
|
|
77
80
|
</div>
|
|
78
81
|
|
|
79
82
|
<section class="panel stack-md">
|