yaml-flow 5.1.0 → 5.2.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/{examples/example-board/reusable-server-runtime.js → board-livecards-server-runtime.js} +42 -20
- package/{examples/example-board/reusable-board-runtime-client.js → browser/board-livecards-runtime-client.js} +6 -2
- package/browser/{board-livegraph-runtime.js → board-livegraph-engine.js} +212 -16
- package/browser/board-livegraph-engine.js.map +1 -0
- package/browser/live-cards.js +362 -38
- package/browser/live-cards.schema.json +20 -4
- package/dist/board-livegraph-runtime/index.cjs +210 -14
- package/dist/board-livegraph-runtime/index.cjs.map +1 -1
- package/dist/board-livegraph-runtime/index.d.cts +49 -5
- package/dist/board-livegraph-runtime/index.d.ts +49 -5
- package/dist/board-livegraph-runtime/index.js +209 -15
- package/dist/board-livegraph-runtime/index.js.map +1 -1
- package/dist/card-compute/index.cjs +63 -7
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +2 -2
- package/dist/card-compute/index.d.ts +2 -2
- package/dist/card-compute/index.js +63 -7
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +664 -75
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +33 -5
- package/dist/cli/board-live-cards-cli.d.ts +33 -5
- package/dist/cli/board-live-cards-cli.js +661 -76
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/{constants-ozjf1Ejw.d.cts → constants-BzZUyYlp.d.cts} +1 -1
- package/dist/{constants-DuzE5n03.d.ts → constants-oCEbNpul.d.ts} +1 -1
- package/dist/continuous-event-graph/index.cjs +47 -14
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +9 -9
- package/dist/continuous-event-graph/index.d.ts +9 -9
- package/dist/continuous-event-graph/index.js +47 -14
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/event-graph/index.cjs +29 -12
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +5 -5
- package/dist/event-graph/index.d.ts +5 -5
- package/dist/event-graph/index.js +29 -12
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +93 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +93 -20
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +29 -12
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.d.cts +2 -2
- package/dist/inference/index.d.ts +2 -2
- package/dist/inference/index.js +29 -12
- package/dist/inference/index.js.map +1 -1
- package/dist/{journal-NLYuqege.d.ts → journal-9HEgs7dU.d.ts} +1 -1
- package/dist/{journal-DRfJiheM.d.cts → journal-B-JCfQnh.d.cts} +1 -1
- package/dist/{live-cards-bridge-Or7fdEJV.d.ts → live-cards-bridge-CeNxiVcm.d.ts} +6 -2
- package/dist/{live-cards-bridge-vGJ6tMzN.d.cts → live-cards-bridge-z_rJCSbi.d.cts} +6 -2
- package/dist/{schedule-CMcZe5Ny.d.ts → schedule-Cszq9LYY.d.ts} +1 -1
- package/dist/{schedule-CiucyCan.d.cts → schedule-qWNL0RQh.d.cts} +1 -1
- package/dist/{types-CMFSIjpc.d.cts → types-BBhqYGhE.d.cts} +4 -0
- package/dist/{types-CMFSIjpc.d.ts → types-BBhqYGhE.d.ts} +4 -0
- package/dist/{types-BzLD8bjb.d.cts → types-CHSdoAAA.d.cts} +1 -1
- package/dist/{types-C2eJ7DAV.d.ts → types-CoW0gQl3.d.ts} +1 -1
- package/dist/{validate-DJQTQ6bP.d.ts → validate-BAVzUJWa.d.ts} +1 -1
- package/dist/{validate-ke92Cleg.d.cts → validate-Dbu7ygys.d.cts} +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +28 -0
- package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +28 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +187 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +139 -5
- package/examples/example-board/agent-instructions-cardlayout.md +28 -0
- package/examples/example-board/agent-instructions.md +603 -0
- package/examples/example-board/cards/card-concentration.json +42 -0
- package/examples/example-board/cards/card-market-prices.json +51 -0
- package/examples/example-board/cards/card-portfolio-action.json +19 -0
- package/examples/example-board/cards/card-portfolio-risks.json +19 -0
- package/examples/example-board/cards/card-portfolio-value.json +62 -0
- package/examples/example-board/cards/card-portfolio.json +44 -0
- package/examples/example-board/demo-chat-handler.js +373 -33
- package/examples/example-board/demo-server-config.json +0 -2
- package/examples/example-board/demo-server.js +46 -7
- package/examples/example-board/demo-shell-browser.html +75 -207
- package/examples/example-board/demo-shell-with-server.html +14 -9
- package/examples/example-board/demo-shell.html +1 -1
- package/examples/example-board/demo-task-executor.js +259 -41
- package/package.json +6 -2
- package/schema/live-cards.schema.json +20 -4
- package/browser/board-livegraph-runtime.js.map +0 -1
- package/examples/example-board/board.yaml +0 -23
- package/examples/example-board/bootstrap_payload.json +0 -1
- package/examples/example-board/cards/card-chain-region-alert.json +0 -39
- package/examples/example-board/cards/card-chain-region-totals.json +0 -26
- package/examples/example-board/cards/card-chain-top-region.json +0 -24
- package/examples/example-board/cards/card-ex-actions.json +0 -32
- package/examples/example-board/cards/card-ex-chart.json +0 -30
- package/examples/example-board/cards/card-ex-filter.json +0 -36
- package/examples/example-board/cards/card-ex-filtered-by-preference.json +0 -59
- package/examples/example-board/cards/card-ex-form.json +0 -91
- package/examples/example-board/cards/card-ex-list.json +0 -22
- package/examples/example-board/cards/card-ex-markdown.json +0 -17
- package/examples/example-board/cards/card-ex-metric.json +0 -19
- package/examples/example-board/cards/card-ex-narrative.json +0 -36
- package/examples/example-board/cards/card-ex-source-http.json +0 -28
- package/examples/example-board/cards/card-ex-source.json +0 -21
- package/examples/example-board/cards/card-ex-status.json +0 -35
- package/examples/example-board/cards/card-ex-table.json +0 -30
- package/examples/example-board/cards/card-ex-todo.json +0 -29
- package/examples/example-board/mock.db +0 -15
- package/examples/example-board/reusable-runtime-artifacts-adapter.js +0 -233
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
const inIdx = argv.indexOf('--in');
|
|
8
|
+
const outIdx = argv.indexOf('--out');
|
|
9
|
+
const errIdx = argv.indexOf('--err');
|
|
10
|
+
const inFile = inIdx !== -1 ? argv[inIdx + 1] : undefined;
|
|
11
|
+
const outFile = outIdx !== -1 ? argv[outIdx + 1] : undefined;
|
|
12
|
+
const errFile = errIdx !== -1 ? argv[errIdx + 1] : undefined;
|
|
13
|
+
if (!inFile || !outFile || !errFile) {
|
|
14
|
+
console.error('Usage: <adapter> run-inference --in <input.json> --out <output.json> --err <error.txt>');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
return { inFile, outFile, errFile };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const envBoardDir = (process.env.BOARD_DIR ?? '').trim();
|
|
21
|
+
|
|
22
|
+
function resolveSyncTmpFileCandidates(payload) {
|
|
23
|
+
const fileName = payload?.context?.card_data?.llm_task_completion_inference?.sync_tmp_file;
|
|
24
|
+
if (typeof fileName !== 'string' || !fileName.trim()) return [];
|
|
25
|
+
|
|
26
|
+
const cleaned = fileName.trim();
|
|
27
|
+
if (path.isAbsolute(cleaned)) {
|
|
28
|
+
return [cleaned];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [
|
|
32
|
+
envBoardDir ? path.join(envBoardDir, cleaned) : '',
|
|
33
|
+
path.join(process.cwd(), cleaned),
|
|
34
|
+
path.join(process.cwd(), 'board-runtime', cleaned),
|
|
35
|
+
path.join(process.cwd(), '..', 'board-runtime', cleaned),
|
|
36
|
+
].filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getReadableTmpFile(tmpCandidates) {
|
|
40
|
+
for (const tmpFile of tmpCandidates) {
|
|
41
|
+
if (!fs.existsSync(tmpFile)) continue;
|
|
42
|
+
const content = fs.readFileSync(tmpFile, 'utf-8').trim();
|
|
43
|
+
if (content) return { tmpFile, content };
|
|
44
|
+
}
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function waitForTmpSyncInput(tmpCandidates, timeoutMs = 120000) {
|
|
49
|
+
const started = Date.now();
|
|
50
|
+
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const interval = setInterval(() => {
|
|
53
|
+
if (Date.now() - started > timeoutMs) {
|
|
54
|
+
clearInterval(interval);
|
|
55
|
+
reject(new Error('Timed out waiting for sync tmp input'));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ready = getReadableTmpFile(tmpCandidates);
|
|
59
|
+
if (!ready) return;
|
|
60
|
+
|
|
61
|
+
clearInterval(interval);
|
|
62
|
+
fs.writeFileSync(ready.tmpFile, '', 'utf-8');
|
|
63
|
+
resolve(ready);
|
|
64
|
+
}, 250);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toNumber(value) {
|
|
69
|
+
const n = Number(value);
|
|
70
|
+
return Number.isFinite(n) ? n : 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildRiskAssessment(table, totalValue) {
|
|
74
|
+
const rows = Array.isArray(table) ? table : [];
|
|
75
|
+
const total = toNumber(totalValue);
|
|
76
|
+
const withWeights = rows.map((row) => {
|
|
77
|
+
const v = toNumber(row?.value);
|
|
78
|
+
return { symbol: String(row?.symbol ?? ''), value: v, weight: total > 0 ? v / total : 0 };
|
|
79
|
+
});
|
|
80
|
+
withWeights.sort((a, b) => b.weight - a.weight);
|
|
81
|
+
const largest = withWeights[0] ?? { symbol: 'N/A', weight: 0, value: 0 };
|
|
82
|
+
const concentrationPct = Math.round(largest.weight * 1000) / 10;
|
|
83
|
+
const concentrationFlag = largest.weight > 0.6;
|
|
84
|
+
const breadthFlag = withWeights.length < 3;
|
|
85
|
+
|
|
86
|
+
const statusText = concentrationFlag
|
|
87
|
+
? `High concentration risk: ${largest.symbol} at ${concentrationPct}% of portfolio value.`
|
|
88
|
+
: `Risk appears moderate: largest position ${largest.symbol} at ${concentrationPct}% of portfolio value.`;
|
|
89
|
+
|
|
90
|
+
const evidence = [
|
|
91
|
+
`positions=${withWeights.length}`,
|
|
92
|
+
`largest=${largest.symbol}`,
|
|
93
|
+
`largest_weight=${concentrationPct}%`,
|
|
94
|
+
`total_value=${Math.round(total * 100) / 100}`,
|
|
95
|
+
`breadth_ok=${withWeights.length >= 3}`,
|
|
96
|
+
`concentration_ok=${largest.weight <= 0.6}`,
|
|
97
|
+
`risk_flag=${breadthFlag || concentrationFlag}`,
|
|
98
|
+
].join('; ');
|
|
99
|
+
|
|
100
|
+
return { isTaskCompleted: true, reason: statusText, evidence };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildRebalancingPlan(table, totalValue, riskAssessment) {
|
|
104
|
+
const rows = Array.isArray(table) ? table : [];
|
|
105
|
+
const total = toNumber(totalValue);
|
|
106
|
+
const targetWeight = rows.length > 0 ? 1 / rows.length : 0;
|
|
107
|
+
const moves = rows
|
|
108
|
+
.map((row) => {
|
|
109
|
+
const symbol = String(row?.symbol ?? 'UNKNOWN');
|
|
110
|
+
const currentValue = toNumber(row?.value);
|
|
111
|
+
const currentWeight = total > 0 ? currentValue / total : 0;
|
|
112
|
+
const deltaWeight = targetWeight - currentWeight;
|
|
113
|
+
return {
|
|
114
|
+
symbol,
|
|
115
|
+
currentWeight,
|
|
116
|
+
deltaWeight,
|
|
117
|
+
action: deltaWeight > 0.03 ? 'BUY' : (deltaWeight < -0.03 ? 'SELL' : 'HOLD'),
|
|
118
|
+
};
|
|
119
|
+
})
|
|
120
|
+
.sort((a, b) => Math.abs(b.deltaWeight) - Math.abs(a.deltaWeight));
|
|
121
|
+
|
|
122
|
+
const topMoves = moves
|
|
123
|
+
.filter((m) => m.action !== 'HOLD')
|
|
124
|
+
.slice(0, 3)
|
|
125
|
+
.map((m) => `${m.action} ${m.symbol} (${Math.round(m.deltaWeight * 1000) / 10}pp)`);
|
|
126
|
+
|
|
127
|
+
const summary = topMoves.length > 0
|
|
128
|
+
? `Rebalance toward equal-weight profile: ${topMoves.join(', ')}.`
|
|
129
|
+
: 'Current allocations are close to equal-weight; no major rebalance needed.';
|
|
130
|
+
|
|
131
|
+
const evidence = [
|
|
132
|
+
`positions=${rows.length}`,
|
|
133
|
+
`target_weight=${Math.round(targetWeight * 1000) / 10}%`,
|
|
134
|
+
`risk_assessment=${typeof riskAssessment === 'string' ? riskAssessment : 'n/a'}`,
|
|
135
|
+
].join('; ');
|
|
136
|
+
|
|
137
|
+
return { isTaskCompleted: true, reason: summary, evidence };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function main() {
|
|
141
|
+
const command = process.argv[2];
|
|
142
|
+
if (command !== 'run-inference') {
|
|
143
|
+
console.error(`Unknown command: ${command ?? '(none)'}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const { inFile, outFile, errFile } = parseArgs(process.argv.slice(3));
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const payload = JSON.parse(fs.readFileSync(inFile, 'utf-8'));
|
|
151
|
+
const tmpCandidates = resolveSyncTmpFileCandidates(payload);
|
|
152
|
+
if (tmpCandidates.length > 0) {
|
|
153
|
+
await waitForTmpSyncInput(tmpCandidates);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const taskName = String(payload?.taskName ?? '');
|
|
157
|
+
const context = payload?.context ?? {};
|
|
158
|
+
// Inputs arrive under context.requires (the card's declared dependencies)
|
|
159
|
+
const requires = context?.requires ?? {};
|
|
160
|
+
const table = requires?.table?.rows ?? requires?.table;
|
|
161
|
+
const totalValue = requires?.totalValue;
|
|
162
|
+
const riskAssessment = requires?.riskAssessment;
|
|
163
|
+
|
|
164
|
+
let result;
|
|
165
|
+
if (taskName === 'portfolio-risk-assessment') {
|
|
166
|
+
result = buildRiskAssessment(table, totalValue);
|
|
167
|
+
} else if (taskName === 'rebalancing-strategy') {
|
|
168
|
+
result = buildRebalancingPlan(table, totalValue, riskAssessment);
|
|
169
|
+
} else {
|
|
170
|
+
result = {
|
|
171
|
+
isTaskCompleted: true,
|
|
172
|
+
reason: `Inference completed for ${taskName || 'unknown-task'}`,
|
|
173
|
+
evidence: 'deterministic-demo-adapter',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
fs.writeFileSync(outFile, JSON.stringify(result), 'utf-8');
|
|
178
|
+
fs.writeFileSync(errFile, '', 'utf-8');
|
|
179
|
+
} catch (err) {
|
|
180
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
181
|
+
fs.writeFileSync(errFile, message, 'utf-8');
|
|
182
|
+
fs.writeFileSync(outFile, JSON.stringify({ isTaskCompleted: false, reason: message, evidence: '' }), 'utf-8');
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
void main();
|
|
@@ -31,6 +31,9 @@ const RUNTIME_ROOT = fs.mkdtempSync(path.join(os.tmpdir(), 'portfolio-tracker-')
|
|
|
31
31
|
const BOARD = path.join(RUNTIME_ROOT, 'board-runtime');
|
|
32
32
|
const CARDS = path.join(RUNTIME_ROOT, 'cards');
|
|
33
33
|
const TMP_FILE = path.join(BOARD, 'tmp_file1');
|
|
34
|
+
const INFERENCE_TMP_FILE_2 = path.join(BOARD, 'tmp_file2');
|
|
35
|
+
const INFERENCE_TMP_FILE_3 = path.join(BOARD, 'tmp_file3');
|
|
36
|
+
const INFERENCE_ADAPTER = path.join(RUNTIME_ROOT, 'portfolio-tracker-inference-adapter.js');
|
|
34
37
|
|
|
35
38
|
function parseArgs(argv) {
|
|
36
39
|
let taskExecutor;
|
|
@@ -112,14 +115,24 @@ function statusText() {
|
|
|
112
115
|
|
|
113
116
|
async function waitForAllCompleted(label, timeoutMs = 30000, pollMs = 500) {
|
|
114
117
|
const start = Date.now();
|
|
118
|
+
const includeInferenceCards = fs.existsSync(path.join(CARDS, 'portfolio-risk-assessment.json'))
|
|
119
|
+
&& fs.existsSync(path.join(CARDS, 'rebalancing-strategy.json'));
|
|
120
|
+
|
|
115
121
|
while (Date.now() - start < timeoutMs) {
|
|
116
122
|
const out = statusText();
|
|
117
|
-
const
|
|
123
|
+
const requiredCards = [
|
|
118
124
|
/\bcompleted\s+portfolio-form\b/.test(out),
|
|
119
125
|
/\bcompleted\s+price-fetch\b/.test(out),
|
|
120
126
|
/\bcompleted\s+holdings-table\b/.test(out),
|
|
121
127
|
/\bcompleted\s+portfolio-value\b/.test(out),
|
|
122
|
-
]
|
|
128
|
+
];
|
|
129
|
+
if (includeInferenceCards) {
|
|
130
|
+
requiredCards.push(
|
|
131
|
+
/\bcompleted\s+portfolio-risk-assessment\b/.test(out),
|
|
132
|
+
/\bcompleted\s+rebalancing-strategy\b/.test(out),
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
const completed = requiredCards.every(Boolean);
|
|
123
136
|
|
|
124
137
|
if (completed) {
|
|
125
138
|
console.log(`${label}: all cards completed.`);
|
|
@@ -142,11 +155,39 @@ function writePrices(prices) {
|
|
|
142
155
|
console.log(`Wrote prices: ${JSON.stringify(prices)}`);
|
|
143
156
|
}
|
|
144
157
|
|
|
158
|
+
function releaseInferenceAdapters(label) {
|
|
159
|
+
if (!fs.existsSync(BOARD)) {
|
|
160
|
+
fs.mkdirSync(BOARD, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
const signal = JSON.stringify({ stage: label, releasedAt: new Date().toISOString() });
|
|
163
|
+
fs.writeFileSync(INFERENCE_TMP_FILE_2, signal, 'utf-8');
|
|
164
|
+
fs.writeFileSync(INFERENCE_TMP_FILE_3, signal, 'utf-8');
|
|
165
|
+
console.log(`Released inference adapters for ${label}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
145
168
|
function setupRuntimeCards() {
|
|
146
169
|
fs.rmSync(CARDS, { recursive: true, force: true });
|
|
147
170
|
fs.mkdirSync(RUNTIME_ROOT, { recursive: true });
|
|
148
171
|
fs.cpSync(CARDS_TEMPLATE, CARDS, { recursive: true });
|
|
149
172
|
fs.copyFileSync(path.join(__dirname, 'fetch-prices.js'), path.join(RUNTIME_ROOT, 'fetch-prices.js'));
|
|
173
|
+
fs.copyFileSync(path.join(__dirname, 'portfolio-tracker-inference-adapter.js'), INFERENCE_ADAPTER);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function printTaskExecutorLog() {
|
|
177
|
+
console.log('\n=== Task Executor Log (board-dir) ===');
|
|
178
|
+
const candidates = fs
|
|
179
|
+
.readdirSync(BOARD, { withFileTypes: true })
|
|
180
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.jsonl') && entry.name.includes('executor'))
|
|
181
|
+
.map(entry => path.join(BOARD, entry.name));
|
|
182
|
+
const taskExecutorLog = candidates.find(p => path.basename(p) === 'task-executor.jsonl') ?? candidates[0];
|
|
183
|
+
if (!taskExecutorLog) {
|
|
184
|
+
console.log(`No task executor log found in board-dir: ${BOARD}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(`Log file: ${taskExecutorLog}`);
|
|
189
|
+
const content = fs.readFileSync(taskExecutorLog, 'utf-8');
|
|
190
|
+
process.stdout.write(content || '(empty)\n');
|
|
150
191
|
}
|
|
151
192
|
|
|
152
193
|
(async () => {
|
|
@@ -156,9 +197,9 @@ function setupRuntimeCards() {
|
|
|
156
197
|
fs.rmSync(BOARD, { recursive: true, force: true });
|
|
157
198
|
|
|
158
199
|
if (options.taskExecutor) {
|
|
159
|
-
cli('init', BOARD, '--task-executor', options.taskExecutor);
|
|
200
|
+
cli('init', BOARD, '--task-executor', options.taskExecutor, '--inference-adapter', INFERENCE_ADAPTER);
|
|
160
201
|
} else {
|
|
161
|
-
cli('init', BOARD);
|
|
202
|
+
cli('init', BOARD, '--inference-adapter', INFERENCE_ADAPTER);
|
|
162
203
|
}
|
|
163
204
|
cli('add-cards', '--rg', BOARD, '--card-glob', path.join(CARDS, '*.json'));
|
|
164
205
|
|
|
@@ -167,6 +208,7 @@ function setupRuntimeCards() {
|
|
|
167
208
|
|
|
168
209
|
console.log('\n=== T1: Writing market prices ===');
|
|
169
210
|
writePrices({ AAPL: 198.50, MSFT: 425.30, GOOG: 178.90, AMZN: 192.40, TSLA: 168.75 });
|
|
211
|
+
releaseInferenceAdapters('T1');
|
|
170
212
|
await waitForAllCompleted('T1');
|
|
171
213
|
|
|
172
214
|
console.log('\n--- T1 Status ---');
|
|
@@ -196,6 +238,7 @@ function setupRuntimeCards() {
|
|
|
196
238
|
cli('update-card', '--rg', BOARD, '--card-id', 'portfolio-form', '--restart');
|
|
197
239
|
await sleep(500);
|
|
198
240
|
writePrices({ AAPL: 198.50, MSFT: 425.30, GOOG: 178.90, AMZN: 192.40, TSLA: 168.75 });
|
|
241
|
+
releaseInferenceAdapters('T2');
|
|
199
242
|
await waitForAllCompleted('T2');
|
|
200
243
|
|
|
201
244
|
console.log('\n--- T2 Status ---');
|
|
@@ -205,13 +248,104 @@ function setupRuntimeCards() {
|
|
|
205
248
|
cli('retrigger', '--rg', BOARD, '--task', 'price-fetch');
|
|
206
249
|
await sleep(500);
|
|
207
250
|
writePrices({ AAPL: 205.00, MSFT: 425.30, GOOG: 178.90, AMZN: 192.40, TSLA: 168.75 });
|
|
251
|
+
releaseInferenceAdapters('T3');
|
|
208
252
|
await waitForAllCompleted('T3');
|
|
209
253
|
|
|
210
254
|
console.log('\n--- T3 Status ---');
|
|
211
255
|
process.stdout.write(statusText());
|
|
212
256
|
|
|
213
|
-
console.log('\n=== T4:
|
|
257
|
+
console.log('\n=== T4: Rapid successive portfolio updates (3x queue stress) ===');
|
|
258
|
+
fs.writeFileSync(TMP_FILE, '', 'utf-8');
|
|
259
|
+
|
|
260
|
+
const portfolioFormV3 = {
|
|
261
|
+
id: 'portfolio-form',
|
|
262
|
+
meta: { title: 'Portfolio Holdings Form' },
|
|
263
|
+
provides: [{ bindTo: 'holdings', src: 'card_data.holdings' }],
|
|
264
|
+
card_data: {
|
|
265
|
+
holdings: [
|
|
266
|
+
{ symbol: 'AAPL', qty: 50 },
|
|
267
|
+
{ symbol: 'MSFT', qty: 30 },
|
|
268
|
+
{ symbol: 'GOOG', qty: 100 },
|
|
269
|
+
{ symbol: 'AMZN', qty: 40 },
|
|
270
|
+
],
|
|
271
|
+
},
|
|
272
|
+
view: {
|
|
273
|
+
elements: [
|
|
274
|
+
{ kind: 'table', label: 'Holdings', data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } },
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const portfolioFormV4 = {
|
|
280
|
+
id: 'portfolio-form',
|
|
281
|
+
meta: { title: 'Portfolio Holdings Form' },
|
|
282
|
+
provides: [{ bindTo: 'holdings', src: 'card_data.holdings' }],
|
|
283
|
+
card_data: {
|
|
284
|
+
holdings: [
|
|
285
|
+
{ symbol: 'AAPL', qty: 45 },
|
|
286
|
+
{ symbol: 'MSFT', qty: 30 },
|
|
287
|
+
{ symbol: 'GOOG', qty: 110 },
|
|
288
|
+
{ symbol: 'TSLA', qty: 60 },
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
view: {
|
|
292
|
+
elements: [
|
|
293
|
+
{ kind: 'table', label: 'Holdings', data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } },
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const portfolioFormV5 = {
|
|
299
|
+
id: 'portfolio-form',
|
|
300
|
+
meta: { title: 'Portfolio Holdings Form' },
|
|
301
|
+
provides: [{ bindTo: 'holdings', src: 'card_data.holdings' }],
|
|
302
|
+
card_data: {
|
|
303
|
+
holdings: [
|
|
304
|
+
{ symbol: 'AAPL', qty: 40 },
|
|
305
|
+
{ symbol: 'MSFT', qty: 35 },
|
|
306
|
+
{ symbol: 'GOOG', qty: 120 },
|
|
307
|
+
{ symbol: 'TSLA', qty: 70 },
|
|
308
|
+
],
|
|
309
|
+
},
|
|
310
|
+
view: {
|
|
311
|
+
elements: [
|
|
312
|
+
{ kind: 'table', label: 'Holdings', data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } },
|
|
313
|
+
],
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// First update starts a source fetch request.
|
|
318
|
+
fs.writeFileSync(portfolioFormPath, JSON.stringify(portfolioFormV3, null, 2));
|
|
319
|
+
cli('update-card', '--rg', BOARD, '--card-id', 'portfolio-form', '--restart');
|
|
320
|
+
|
|
321
|
+
// Immediate second update should queue a newer checksum while the first request is in-flight.
|
|
322
|
+
fs.writeFileSync(portfolioFormPath, JSON.stringify(portfolioFormV4, null, 2));
|
|
323
|
+
cli('update-card', '--rg', BOARD, '--card-id', 'portfolio-form', '--restart');
|
|
324
|
+
|
|
325
|
+
// Immediate third update should overwrite queued checksum (latest-state wins).
|
|
326
|
+
fs.writeFileSync(portfolioFormPath, JSON.stringify(portfolioFormV5, null, 2));
|
|
327
|
+
cli('update-card', '--rg', BOARD, '--card-id', 'portfolio-form', '--restart');
|
|
328
|
+
|
|
329
|
+
// 7) wait for first request, then 8) write response prices for update #1 tickers.
|
|
330
|
+
// await readFetchRequest('T4 first fetch', ['AAPL', 'MSFT', 'GOOG', 'AMZN']);
|
|
331
|
+
writePrices({ AAPL: 205.00, MSFT: 425.30, GOOG: 178.90, AMZN: 192.40 });
|
|
332
|
+
releaseInferenceAdapters('T4-first');
|
|
333
|
+
await sleep(5000);
|
|
334
|
+
|
|
335
|
+
// 9) wait for second request, then 10) write response prices for update #5 tickers.
|
|
336
|
+
// await readFetchRequest('T4 second fetch', ['AAPL', 'MSFT', 'GOOG', 'TSLA']);
|
|
337
|
+
writePrices({ AAPL: 206.00, MSFT: 426.00, GOOG: 179.50, TSLA: 169.20 });
|
|
338
|
+
releaseInferenceAdapters('T4-second');
|
|
339
|
+
|
|
340
|
+
await waitForAllCompleted('T4');
|
|
341
|
+
|
|
342
|
+
console.log('\n--- T4 Status ---');
|
|
343
|
+
process.stdout.write(statusText());
|
|
344
|
+
|
|
345
|
+
console.log('\n=== T5: Final board status ===');
|
|
214
346
|
process.stdout.write(statusText());
|
|
215
347
|
|
|
348
|
+
printTaskExecutorLog();
|
|
349
|
+
|
|
216
350
|
console.log('\nPortfolio tracker completed successfully');
|
|
217
351
|
})();
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Card Design Principles & Layout Guide
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Card Design Principles
|
|
6
|
+
|
|
7
|
+
- **Cards are not pages** — think post-it notes, not dashboards. The primary content (table, editable data) should be immediately visible, not buried below stacked metrics. Use at most one hero `metric`; collapse secondary summary figures into a single `text` line rather than multiple `metric` blocks.
|
|
8
|
+
- **Single responsibility** — each card answers one question. If the title needs "and", split it.
|
|
9
|
+
- **No redundancy across cards** — each column on a board should appear on exactly one card. If a value is already visible elsewhere, omit it; the user's eye can join cards mentally.
|
|
10
|
+
- **Aggregations are distinct** — a metric that summarises data from another card (total, count, average) is not redundant — it is new information. Keep it.
|
|
11
|
+
- **Separate input from output** — cards with editable elements (`editable-table`, `form`, `filter`) should stay lean; put heavy compute and display in a separate downstream card that `requires` the published token.
|
|
12
|
+
- **Propagate data, not display** — use `provides` to pass data between cards; never duplicate a `sources[]` fetch for data another card already provides.
|
|
13
|
+
- **KISS** — if you are unsure whether a field adds value, leave it out. A sparse card that is immediately readable is better than a dense card that requires study.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Layout
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
"layout": {
|
|
21
|
+
"board": { "col": 4, "order": 5 },
|
|
22
|
+
"canvas": { "x": 300, "y": 400, "w": 280, "h": 180 }
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `board.col` — Bootstrap 12-column span: `3`=quarter, `4`=third, `6`=half, `8`=two-thirds, `12`=full
|
|
27
|
+
- `board.order` — ascending integer, controls vertical sort in board view
|
|
28
|
+
- `canvas` — pixel coordinates/size for drag-layout (canvas mode). `h` must be tall enough for all rendered content — a card with metrics + a 4-row table typically needs 400–500px. Too small a height causes an in-card scrollbar; when in doubt, size generously.
|