@cspeach/cli 1.1.1 → 1.1.3
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/dist/agent/loop.js +33 -4
- package/dist/agent/session-context.js +24 -0
- package/dist/agent/tool-dispatch.js +17 -1
- package/dist/approvals/jwt.js +12 -1
- package/dist/cli-args.js +24 -0
- package/dist/cli.js +33 -4
- package/dist/commands/config-set.js +139 -4
- package/dist/config/loader.js +34 -0
- package/dist/cost/pricing.js +4 -0
- package/dist/cost/session-cost.js +28 -0
- package/dist/doctor/checks/_standalone-skip.js +37 -0
- package/dist/doctor/checks/cert.js +5 -0
- package/dist/doctor/checks/forge-rules.js +6 -0
- package/dist/doctor/checks/sap.js +5 -0
- package/dist/doctor/checks/standards.js +27 -0
- package/dist/doctor/checks/system-roles.js +5 -0
- package/dist/doctor/checks/zcspeach.js +5 -0
- package/dist/doctor/run.js +2 -0
- package/dist/one-shot.js +36 -11
- package/dist/renderer/abap-inline.js +2 -1
- package/dist/renderer/syntax.js +7 -1
- package/dist/repl/current-transport.js +23 -1
- package/dist/repl/post-turn-status.js +9 -6
- package/dist/repl.js +40 -7
- package/dist/sap/offline-adt-client.js +58 -0
- package/dist/sap/standalone-onboarding.js +118 -0
- package/dist/sap/standalone-profile.js +237 -0
- package/dist/skills/bundled-skills.js +41 -41
- package/dist/standards/standards-file.js +107 -0
- package/dist/standards/standards-init.js +194 -0
- package/dist/tools/capability/tool.js +2 -0
- package/dist/tools/fiori/fe-scaffold.js +25 -0
- package/dist/tools/fiori/smoke/assertions.js +8 -2
- package/dist/tools/fiori/smoke/driver.js +6 -0
- package/dist/tools/fiori/smoke/run-smoke.js +69 -2
- package/dist/tools/fiori/tools.js +4 -2
- package/dist/tools/index.js +25 -0
- package/dist/tools/sap-read.js +18 -0
- package/dist/tools/sap-write.js +32 -0
- package/dist/tools/snapshot.js +3 -0
- package/dist/tools/todo.js +11 -3
- package/dist/tools/transport.js +4 -0
- package/dist/ui/app.js +1 -1
- package/dist/ui/sap-state-store.js +1 -1
- package/dist/ui/status-line.js +2 -2
- package/dist/ui/todo-panel.js +27 -6
- package/fiori/zlas-inspection-lr/package.json +26 -0
- package/fiori/zlas-inspection-lr/ui5-local.yaml +45 -0
- package/fiori/zlas-inspection-lr/ui5.yaml +41 -0
- package/fiori/zlas-inspection-lr/webapp/Component.js +12 -0
- package/fiori/zlas-inspection-lr/webapp/i18n/i18n.properties +9 -0
- package/fiori/zlas-inspection-lr/webapp/index.html +35 -0
- package/fiori/zlas-inspection-lr/webapp/manifest.json +145 -0
- package/package.json +109 -109
package/dist/tools/sap-write.js
CHANGED
|
@@ -32,6 +32,10 @@ import { auditLog } from '@cspeach/sap-client';
|
|
|
32
32
|
import { ERR } from '../errors/codes.js';
|
|
33
33
|
import { interceptUpdateMethod, CancelledByUserError, } from './update-method-intercept.js';
|
|
34
34
|
import { resolveWriteTransport } from './transport-resolution.js';
|
|
35
|
+
// F-L2-1: statusline-only wire — records the transport a write ACTUALLY
|
|
36
|
+
// stamped so the `tr` segment stops reading `none` for plan-supplied
|
|
37
|
+
// transports. Never touches the session transport pointer.
|
|
38
|
+
import { noteWriteTransport } from '../repl/current-transport.js';
|
|
35
39
|
import { presentSafetyConfirmation } from '../repl/safety-confirm.js';
|
|
36
40
|
import { isSafetyConfirmEnabled } from '../repl/safety-mode-state.js';
|
|
37
41
|
// ── Transcript visibility helpers (D16-display + tour slide-7, 2026-06-12) ──
|
|
@@ -85,6 +89,7 @@ registerTool({
|
|
|
85
89
|
+ 'a single method body. For method-body-only edits use sap_update_method (Rule 7a). '
|
|
86
90
|
+ 'The transport auto-resolves to the owning request if the object is already locked.',
|
|
87
91
|
isMutating: true,
|
|
92
|
+
requiresSap: true,
|
|
88
93
|
input_schema: {
|
|
89
94
|
type: 'object',
|
|
90
95
|
properties: {
|
|
@@ -208,6 +213,7 @@ registerTool({
|
|
|
208
213
|
};
|
|
209
214
|
}
|
|
210
215
|
await auditLog('setSource', args.type, args.name, 'success', Date.now() - startedAt);
|
|
216
|
+
noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
|
|
211
217
|
// ── Gate 4: Verify — syntax check (Rule 10) ──────────────────────────────
|
|
212
218
|
const syntax = await verifySyntax(ctx.adt, args.name, args.type);
|
|
213
219
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify({ written: true, syntax }), false, Date.now() - startedAt);
|
|
@@ -247,6 +253,7 @@ registerTool({
|
|
|
247
253
|
+ 'method_source must be the lines between METHOD x. and ENDMETHOD. (no wrappers). '
|
|
248
254
|
+ 'The transport auto-resolves to the owning request if the class is already locked.',
|
|
249
255
|
isMutating: true,
|
|
256
|
+
requiresSap: true,
|
|
250
257
|
input_schema: {
|
|
251
258
|
type: 'object',
|
|
252
259
|
properties: {
|
|
@@ -400,6 +407,7 @@ registerTool({
|
|
|
400
407
|
};
|
|
401
408
|
}
|
|
402
409
|
await auditLog('updateMethod', 'CLAS', args.class_name, 'success', Date.now() - startedAt, `method=${args.method_name}`);
|
|
410
|
+
noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
|
|
403
411
|
// ── Gate 4: Verify — syntax check (Rule 10) ──────────────────────────────
|
|
404
412
|
const syntax = await verifySyntax(ctx.adt, args.class_name, 'CLAS');
|
|
405
413
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify({ updated: true, syntax }), false, Date.now() - startedAt);
|
|
@@ -435,6 +443,7 @@ registerTool({
|
|
|
435
443
|
+ 'Post-check: confirms the object no longer exists. '
|
|
436
444
|
+ 'The transport auto-resolves to the owning request if the object is already locked.',
|
|
437
445
|
isMutating: true,
|
|
446
|
+
requiresSap: true,
|
|
438
447
|
input_schema: {
|
|
439
448
|
type: 'object',
|
|
440
449
|
properties: {
|
|
@@ -530,6 +539,7 @@ registerTool({
|
|
|
530
539
|
};
|
|
531
540
|
}
|
|
532
541
|
await auditLog('deleteObject', args.type, args.name, 'success', Date.now() - startedAt);
|
|
542
|
+
noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
|
|
533
543
|
// ── Post-check: object should no longer exist ────────────────────────────
|
|
534
544
|
const stillExists = await ctx.adt.objectStructure(args.type, args.name)
|
|
535
545
|
.then(() => true)
|
|
@@ -556,6 +566,7 @@ registerTool({
|
|
|
556
566
|
+ 'Requires approval_id. No snapshot needed (new object). '
|
|
557
567
|
+ 'Write source separately via sap_set_source, then call sap_activate.',
|
|
558
568
|
isMutating: true,
|
|
569
|
+
requiresSap: true,
|
|
559
570
|
input_schema: {
|
|
560
571
|
type: 'object',
|
|
561
572
|
properties: {
|
|
@@ -630,6 +641,9 @@ registerTool({
|
|
|
630
641
|
is_error: true,
|
|
631
642
|
};
|
|
632
643
|
}
|
|
644
|
+
// F-L2-1 statusline wire. This path never reaches resolveWriteTransport —
|
|
645
|
+
// the 3-tier effectiveTransport above is what SAP stamped.
|
|
646
|
+
noteWriteTransport(effectiveTransport);
|
|
633
647
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), false, Date.now() - startedAt);
|
|
634
648
|
return {
|
|
635
649
|
content: JSON.stringify({
|
|
@@ -660,6 +674,7 @@ registerTool({
|
|
|
660
674
|
+ 'After writing, activate the class via sap_activate. '
|
|
661
675
|
+ 'The transport auto-resolves to the owning request if the class is already locked.',
|
|
662
676
|
isMutating: true,
|
|
677
|
+
requiresSap: true,
|
|
663
678
|
input_schema: {
|
|
664
679
|
type: 'object',
|
|
665
680
|
properties: {
|
|
@@ -752,6 +767,7 @@ registerTool({
|
|
|
752
767
|
};
|
|
753
768
|
}
|
|
754
769
|
await auditLog('setClassInclude', 'CLAS', args.className, 'success', Date.now() - startedAt, `include=${args.includeType}`);
|
|
770
|
+
noteWriteTransport(resolved.transport); // F-L2-1 statusline wire
|
|
755
771
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify({ written: true, variant: result.variantUsed }), false, Date.now() - startedAt);
|
|
756
772
|
// Tour slide-7 chain. Honest states: includes have no standalone syntax
|
|
757
773
|
// gate (checked at activation), so ③ renders as deferred, never faked.
|
|
@@ -781,6 +797,7 @@ registerTool({
|
|
|
781
797
|
+ 'Requires approval_id. No snapshot needed (new object). '
|
|
782
798
|
+ 'Use the domain in a data element via sap_create_data_element.',
|
|
783
799
|
isMutating: true,
|
|
800
|
+
requiresSap: true,
|
|
784
801
|
input_schema: {
|
|
785
802
|
type: 'object',
|
|
786
803
|
properties: {
|
|
@@ -879,6 +896,11 @@ registerTool({
|
|
|
879
896
|
is_error: true,
|
|
880
897
|
};
|
|
881
898
|
}
|
|
899
|
+
// F-L2-1 statusline wire. Recorded on CREATED, not on active: the object is
|
|
900
|
+
// already in effectiveTransport by then, so a create-succeeded /
|
|
901
|
+
// activation-failed run must still show which request holds it. DDIC creates
|
|
902
|
+
// bypass resolveWriteTransport — effectiveTransport is what SAP stamped.
|
|
903
|
+
noteWriteTransport(effectiveTransport);
|
|
882
904
|
if (!result.active) {
|
|
883
905
|
// Rule 10: created but did not activate — surface as an error.
|
|
884
906
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), true, Date.now() - startedAt);
|
|
@@ -910,6 +932,7 @@ registerTool({
|
|
|
910
932
|
+ 'activate and verify it. Requires approval_id. No snapshot needed (new object). '
|
|
911
933
|
+ 'Use the data element as a table/structure field type.',
|
|
912
934
|
isMutating: true,
|
|
935
|
+
requiresSap: true,
|
|
913
936
|
input_schema: {
|
|
914
937
|
type: 'object',
|
|
915
938
|
properties: {
|
|
@@ -1002,6 +1025,8 @@ registerTool({
|
|
|
1002
1025
|
is_error: true,
|
|
1003
1026
|
};
|
|
1004
1027
|
}
|
|
1028
|
+
// F-L2-1 statusline wire — see the createDomain note above (created, not active).
|
|
1029
|
+
noteWriteTransport(effectiveTransport);
|
|
1005
1030
|
if (!result.active) {
|
|
1006
1031
|
// Rule 10: created but did not activate — surface as an error.
|
|
1007
1032
|
await finalizeToolCall(ctx.session, toolUseId, JSON.stringify(result), true, Date.now() - startedAt);
|
|
@@ -1036,6 +1061,7 @@ registerTool({
|
|
|
1036
1061
|
+ 'sap_update_method / sap_create_object (Rule 10). '
|
|
1037
1062
|
+ 'Post-check: objects must no longer appear in the inactive-objects list.',
|
|
1038
1063
|
isMutating: true,
|
|
1064
|
+
requiresSap: true,
|
|
1039
1065
|
input_schema: {
|
|
1040
1066
|
type: 'object',
|
|
1041
1067
|
properties: {
|
|
@@ -1151,6 +1177,7 @@ registerTool({
|
|
|
1151
1177
|
name: 'sap_lock',
|
|
1152
1178
|
description: 'RARELY needed. Returns a lock handle but other write tools cannot consume it — sap_set_source / sap_update_method / sap_delete_object each acquire their OWN lock internally. Calling sap_lock as a recovery step after a write failure CREATES A STALE LOCK that blocks the next write attempt; do not do that. Only call this when you need to inspect lock state or hold a lock for a coordinated multi-step ABAP workbench operation outside of CSPeach.',
|
|
1153
1179
|
isMutating: true,
|
|
1180
|
+
requiresSap: true,
|
|
1154
1181
|
input_schema: {
|
|
1155
1182
|
type: 'object',
|
|
1156
1183
|
properties: {
|
|
@@ -1172,6 +1199,7 @@ registerTool({
|
|
|
1172
1199
|
name: 'sap_unlock',
|
|
1173
1200
|
description: 'Release an ADT edit lock on an ABAP object using the lockHandle from sap_lock.',
|
|
1174
1201
|
isMutating: true,
|
|
1202
|
+
requiresSap: true,
|
|
1175
1203
|
input_schema: {
|
|
1176
1204
|
type: 'object',
|
|
1177
1205
|
properties: {
|
|
@@ -1194,6 +1222,7 @@ registerTool({
|
|
|
1194
1222
|
name: 'sap_service_binding_publish',
|
|
1195
1223
|
description: 'Publish an ADT service binding so its OData endpoint becomes active and reachable.',
|
|
1196
1224
|
isMutating: true,
|
|
1225
|
+
requiresSap: true,
|
|
1197
1226
|
input_schema: {
|
|
1198
1227
|
type: 'object',
|
|
1199
1228
|
properties: {
|
|
@@ -1217,6 +1246,7 @@ registerTool({
|
|
|
1217
1246
|
description: 'Unpublish an ADT service binding so its OData endpoint is removed. '
|
|
1218
1247
|
+ 'Required before deleting a published binding (SAP returns HTTP 412 "is published" otherwise).',
|
|
1219
1248
|
isMutating: true,
|
|
1249
|
+
requiresSap: true,
|
|
1220
1250
|
input_schema: {
|
|
1221
1251
|
type: 'object',
|
|
1222
1252
|
properties: {
|
|
@@ -1239,6 +1269,7 @@ registerTool({
|
|
|
1239
1269
|
+ 'Maintaining MSAG message-class entries via ADT requires XML message-class-format '
|
|
1240
1270
|
+ 'parsing not yet implemented in @cspeach/sap-client.',
|
|
1241
1271
|
isMutating: true,
|
|
1272
|
+
requiresSap: true,
|
|
1242
1273
|
input_schema: {
|
|
1243
1274
|
type: 'object',
|
|
1244
1275
|
properties: {
|
|
@@ -1269,6 +1300,7 @@ registerTool({
|
|
|
1269
1300
|
+ 'Writing NROB number-range intervals via ADT requires NR-object XML format '
|
|
1270
1301
|
+ 'not yet implemented in @cspeach/sap-client.',
|
|
1271
1302
|
isMutating: true,
|
|
1303
|
+
requiresSap: true,
|
|
1272
1304
|
input_schema: {
|
|
1273
1305
|
type: 'object',
|
|
1274
1306
|
properties: {
|
package/dist/tools/snapshot.js
CHANGED
|
@@ -30,6 +30,9 @@ registerTool({
|
|
|
30
30
|
name: 'sap_snapshot_take',
|
|
31
31
|
description: "Take a snapshot of an object's current source before modification. Usually invoked automatically by the CLI before any write — rarely called directly by the model.",
|
|
32
32
|
isMutating: false,
|
|
33
|
+
// Reads the live source via ctx.adt.getSource — the only snapshot tool that
|
|
34
|
+
// needs SAP. list / read / cleanup work purely on the local snapshot store.
|
|
35
|
+
requiresSap: true,
|
|
33
36
|
input_schema: {
|
|
34
37
|
type: 'object',
|
|
35
38
|
properties: {
|
package/dist/tools/todo.js
CHANGED
|
@@ -97,7 +97,11 @@ export async function todoSetHandler(args, ctx) {
|
|
|
97
97
|
ctx.chunkEmitter?.emit('chunk', formatTodoChecklistBlock(todos));
|
|
98
98
|
const result = {
|
|
99
99
|
count: todos.length,
|
|
100
|
-
|
|
100
|
+
// LAST-wins, matching the app strip and Ctrl+T panel (both `.at(-1)`).
|
|
101
|
+
// With a batched transition (several items flipped in one call) first-wins
|
|
102
|
+
// would tell the model step 2 is running while the user's ▶ points at
|
|
103
|
+
// step 5 — the echo and the UI must name the same item.
|
|
104
|
+
in_progress: inProgress.at(-1)?.text ?? null,
|
|
101
105
|
};
|
|
102
106
|
if (inProgress.length > 1) {
|
|
103
107
|
result.warning =
|
|
@@ -108,10 +112,14 @@ export async function todoSetHandler(args, ctx) {
|
|
|
108
112
|
}
|
|
109
113
|
registerTool({
|
|
110
114
|
name: 'todo_set',
|
|
111
|
-
// Model-behavior lever — this exact framing is binding (task-4 brief
|
|
115
|
+
// Model-behavior lever — this exact framing is binding (task-4 brief;
|
|
116
|
+
// the no-batching nudge is binding per the F-L2-3 task-5 brief).
|
|
112
117
|
description: 'Maintain your task list for multi-step work. Call with the FULL updated list whenever you ' +
|
|
113
118
|
'complete a step, start a new one, or discover new work. Exactly one item should be ' +
|
|
114
|
-
'in_progress at a time. The user sees this list — keep items short and outcome-shaped.'
|
|
119
|
+
'in_progress at a time. The user sees this list — keep items short and outcome-shaped. ' +
|
|
120
|
+
'Update at EVERY step transition: when you finish a step, immediately call todo_set ' +
|
|
121
|
+
'marking it completed and the next step in_progress. Never batch several finished steps ' +
|
|
122
|
+
'into one later call — the user watches the ▶ indicator live.',
|
|
115
123
|
isMutating: false,
|
|
116
124
|
category: 'session',
|
|
117
125
|
input_schema: {
|
package/dist/tools/transport.js
CHANGED
|
@@ -63,6 +63,7 @@ registerTool({
|
|
|
63
63
|
+ 'approval mismatch failure mode observed live 2026-05-07). '
|
|
64
64
|
+ 'Requires approval_id. Returns the transport number to use in subsequent writes.',
|
|
65
65
|
isMutating: true,
|
|
66
|
+
requiresSap: true,
|
|
66
67
|
input_schema: {
|
|
67
68
|
type: 'object',
|
|
68
69
|
properties: {
|
|
@@ -260,6 +261,7 @@ registerTool({
|
|
|
260
261
|
+ 'locked → use the returned owning transport; not locked → a new or '
|
|
261
262
|
+ 'existing transport may be used.',
|
|
262
263
|
isMutating: false,
|
|
264
|
+
requiresSap: true,
|
|
263
265
|
input_schema: {
|
|
264
266
|
type: 'object',
|
|
265
267
|
properties: {
|
|
@@ -330,6 +332,7 @@ registerTool({
|
|
|
330
332
|
description: 'List SAP CTS transport requests. '
|
|
331
333
|
+ 'Optionally filter by owner (user) or status (modifiable | released).',
|
|
332
334
|
isMutating: false,
|
|
335
|
+
requiresSap: true,
|
|
333
336
|
input_schema: {
|
|
334
337
|
type: 'object',
|
|
335
338
|
properties: {
|
|
@@ -377,6 +380,7 @@ registerTool({
|
|
|
377
380
|
+ 'All objects must be syntax-clean and ATC-clean before releasing (Rule 9). '
|
|
378
381
|
+ 'Requires approval_id.',
|
|
379
382
|
isMutating: true,
|
|
383
|
+
requiresSap: true,
|
|
380
384
|
input_schema: {
|
|
381
385
|
type: 'object',
|
|
382
386
|
properties: {
|
package/dist/ui/app.js
CHANGED
|
@@ -510,7 +510,7 @@ export function App({ chunkEmitter, onSubmit, onSlashTrigger, onHistoryTrigger,
|
|
|
510
510
|
askQuestionPendingRef.current = null;
|
|
511
511
|
setAskQuestionPending(null);
|
|
512
512
|
}
|
|
513
|
-
} }, askQuestionSeq))), _jsx(TurnStatus, { inProgressTodo: todos.
|
|
513
|
+
} }, askQuestionSeq))), _jsx(TurnStatus, { inProgressTodo: todos.filter((t) => t.status === 'in_progress').at(-1)?.text ?? null }), _jsx(StatusLine, {}), _jsx(Footer, { onSubmit: beginSubmit, onSlashTrigger: handleSlashTrigger, onHistoryTrigger: handleHistoryTrigger, prefill: prefill, onValueChange: setFooterValue, onBufferChange: setFooterBufferCount,
|
|
514
514
|
// Task 7 — multiline announcements (draft discarded) land as
|
|
515
515
|
// dim scrollback lines, house style for Footer-adjacent notices.
|
|
516
516
|
onNotice: (t) => chunkEmitter.emit('chunk', `\x1b[2m${t}\x1b[22m\n`),
|
|
@@ -14,7 +14,7 @@ import React, { createContext, useContext } from 'react';
|
|
|
14
14
|
const DEFAULT_STATE = {
|
|
15
15
|
system: '', user: '', transport: null, locksHeld: [],
|
|
16
16
|
tokenUsageToday: 0, dollarSpendToday: 0, dollarCap: 100, recentTurns: [],
|
|
17
|
-
writeMode: null,
|
|
17
|
+
writeMode: null, lastWriteTransport: null,
|
|
18
18
|
};
|
|
19
19
|
/**
|
|
20
20
|
* Rate limiter: calls `fn` at most once every `minIntervalMs`. Trailing-only:
|
package/dist/ui/status-line.js
CHANGED
|
@@ -37,7 +37,7 @@ export function StatusLine() {
|
|
|
37
37
|
// the field lengths.
|
|
38
38
|
return (_jsxs(Text, { wrap: "truncate-end", children: [s.system
|
|
39
39
|
? _jsx(Text, { color: "cyan", children: s.system })
|
|
40
|
-
: _jsx(Text, { dimColor: true, children: "no system" }), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: "white", children: s.user || '—' }), _jsx(Text, { dimColor: true, children: " \u2502 tr " }), s.transport
|
|
41
|
-
? _jsx(Text, { color: "yellow", children: s.transport })
|
|
40
|
+
: _jsx(Text, { dimColor: true, children: "no system" }), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: "white", children: s.user || '—' }), _jsx(Text, { dimColor: true, children: " \u2502 tr " }), (s.transport ?? s.lastWriteTransport)
|
|
41
|
+
? _jsx(Text, { color: "yellow", children: s.transport ?? s.lastWriteTransport })
|
|
42
42
|
: _jsx(Text, { dimColor: true, children: "none" }), s.writeMode && (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: MODE_COLORS[s.writeMode], children: s.writeMode })] })), _jsx(Text, { dimColor: true, children: " \u2502 " }), _jsx(Text, { color: "green", children: spendDisplay })] }));
|
|
43
43
|
}
|
package/dist/ui/todo-panel.js
CHANGED
|
@@ -16,8 +16,10 @@ import { Box, Text, useInput } from 'ink';
|
|
|
16
16
|
*
|
|
17
17
|
* Lists longer than TODO_VISIBLE_ROWS get a scrolling window
|
|
18
18
|
* (CommandPalette pattern): ↑/↓ move the window, hidden counts show as
|
|
19
|
-
* "↑ N more" / "↓ N more". The initial window anchors the in_progress
|
|
19
|
+
* "↑ N more" / "↓ N more". The initial window anchors the live in_progress
|
|
20
20
|
* item ~3 rows from the top so the current step is visible on open.
|
|
21
|
+
* "Live" = the LAST in_progress item in list order (F-L2-3) — see
|
|
22
|
+
* activeTodoIndex below.
|
|
21
23
|
*/
|
|
22
24
|
const PEACH = '#FFCBA4';
|
|
23
25
|
export const TODO_VISIBLE_ROWS = 12;
|
|
@@ -28,15 +30,31 @@ export function formatTodoCounts(todos) {
|
|
|
28
30
|
const open = todos.filter((t) => t.status === 'pending').length;
|
|
29
31
|
return `Tasks — ${done} done · ${active} active · ${open} open`;
|
|
30
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Index of the LIVE step — the LAST in_progress item in list order, or -1.
|
|
35
|
+
*
|
|
36
|
+
* F-L2-3 (sitting #1): todo_set tolerates several in_progress items (a
|
|
37
|
+
* warning, not an error — todo.ts), and models routinely batch transitions,
|
|
38
|
+
* leaving an earlier item still marked in_progress. List order IS plan
|
|
39
|
+
* order, so the later item is the one actually being worked; picking the
|
|
40
|
+
* FIRST match pinned the ▶ on a step already long finished.
|
|
41
|
+
*/
|
|
42
|
+
export function activeTodoIndex(todos) {
|
|
43
|
+
for (let i = todos.length - 1; i >= 0; i--) {
|
|
44
|
+
if (todos[i].status === 'in_progress')
|
|
45
|
+
return i;
|
|
46
|
+
}
|
|
47
|
+
return -1;
|
|
48
|
+
}
|
|
31
49
|
export function TodoPanel({ todos, onClose }) {
|
|
32
50
|
const maxOffset = Math.max(0, todos.length - TODO_VISIBLE_ROWS);
|
|
33
|
-
|
|
51
|
+
const activeIdx = activeTodoIndex(todos);
|
|
52
|
+
// Anchor the initial window so the live in_progress item sits ~3 rows from
|
|
34
53
|
// the top (CommandPalette's cursor rule, with the active item as cursor).
|
|
35
54
|
const [offset, setOffset] = useState(() => {
|
|
36
|
-
|
|
37
|
-
if (idx <= 3)
|
|
55
|
+
if (activeIdx <= 3)
|
|
38
56
|
return 0;
|
|
39
|
-
return Math.min(
|
|
57
|
+
return Math.min(activeIdx - 3, maxOffset);
|
|
40
58
|
});
|
|
41
59
|
useInput((input, key) => {
|
|
42
60
|
if (key.escape || input === 'q' || (key.ctrl && (input === 't' || input === 'T'))) {
|
|
@@ -56,7 +74,10 @@ export function TodoPanel({ todos, onClose }) {
|
|
|
56
74
|
if (t.status === 'completed') {
|
|
57
75
|
return _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: ["\u25A3 ", t.text] }, key);
|
|
58
76
|
}
|
|
59
|
-
|
|
77
|
+
// Only the LIVE step gets the ▶; a stale earlier in_progress item
|
|
78
|
+
// (batched transition) renders as an ordinary open row, so the
|
|
79
|
+
// panel never shows two arrows or points at finished work.
|
|
80
|
+
if (t.status === 'in_progress' && clamped + i === activeIdx) {
|
|
60
81
|
return _jsxs(Text, { color: PEACH, bold: true, wrap: "truncate-end", children: ["\u25B6 ", t.text] }, key);
|
|
61
82
|
}
|
|
62
83
|
return _jsxs(Text, { wrap: "truncate-end", children: ["\u25A1 ", t.text] }, key);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zlas.inspectionlog",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Description of zlas.inspectionlog.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ui5",
|
|
7
|
+
"openui5",
|
|
8
|
+
"sapui5"
|
|
9
|
+
],
|
|
10
|
+
"main": "webapp/index.html",
|
|
11
|
+
"dependencies": {},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@ui5/cli": "^4.0.33",
|
|
14
|
+
"@sap/ux-ui5-tooling": "1"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "fiori run --open \"/test/flpSandbox.html?sap-ui-xx-viewCache=false#zlasinspectionlog-tile\"",
|
|
18
|
+
"start-local": "fiori run --config ./ui5-local.yaml --open \"/test/flpSandbox.html?sap-ui-xx-viewCache=false#zlasinspectionlog-tile\"",
|
|
19
|
+
"build": "ui5 build --config=ui5.yaml --clean-dest --dest dist",
|
|
20
|
+
"deploy": "fiori verify",
|
|
21
|
+
"deploy-config": "fiori add deploy-config",
|
|
22
|
+
"start-noflp": "fiori run --open \"/index.html?sap-ui-xx-viewCache=false\"",
|
|
23
|
+
"start-variants-management": "fiori run --open \"/preview.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true#zlasinspectionlog-tile\""
|
|
24
|
+
},
|
|
25
|
+
"sapux": true
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
|
|
2
|
+
|
|
3
|
+
specVersion: "4.0"
|
|
4
|
+
metadata:
|
|
5
|
+
name: zlas.inspectionlog
|
|
6
|
+
type: application
|
|
7
|
+
framework:
|
|
8
|
+
name: SAPUI5
|
|
9
|
+
version: 1.120.0
|
|
10
|
+
libraries:
|
|
11
|
+
- name: sap.m
|
|
12
|
+
- name: sap.ui.core
|
|
13
|
+
- name: sap.fe.templates
|
|
14
|
+
- name: sap.ushell
|
|
15
|
+
- name: themelib_sap_fiori_3
|
|
16
|
+
server:
|
|
17
|
+
customMiddleware:
|
|
18
|
+
- name: fiori-tools-appreload
|
|
19
|
+
afterMiddleware: compression
|
|
20
|
+
configuration:
|
|
21
|
+
port: 35729
|
|
22
|
+
path: webapp
|
|
23
|
+
delay: 300
|
|
24
|
+
- name: fiori-tools-preview
|
|
25
|
+
afterMiddleware: fiori-tools-appreload
|
|
26
|
+
configuration:
|
|
27
|
+
flp:
|
|
28
|
+
theme: sap_fiori_3
|
|
29
|
+
path: test/flpSandbox.html
|
|
30
|
+
intent:
|
|
31
|
+
object: zlasinspectionlog
|
|
32
|
+
action: tile
|
|
33
|
+
- name: fiori-tools-proxy
|
|
34
|
+
afterMiddleware: compression
|
|
35
|
+
configuration:
|
|
36
|
+
ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted
|
|
37
|
+
backend:
|
|
38
|
+
- path: /sap
|
|
39
|
+
url: https://172.16.24.98:44300
|
|
40
|
+
client: '100'
|
|
41
|
+
builder:
|
|
42
|
+
resources:
|
|
43
|
+
excludes:
|
|
44
|
+
- /test/**
|
|
45
|
+
- /localService/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
|
|
2
|
+
|
|
3
|
+
specVersion: "4.0"
|
|
4
|
+
metadata:
|
|
5
|
+
name: zlas.inspectionlog
|
|
6
|
+
type: application
|
|
7
|
+
server:
|
|
8
|
+
customMiddleware:
|
|
9
|
+
- name: fiori-tools-proxy
|
|
10
|
+
afterMiddleware: compression
|
|
11
|
+
configuration:
|
|
12
|
+
ignoreCertErrors: true # Dev-only: accept the backend's self-signed certificate
|
|
13
|
+
ui5:
|
|
14
|
+
path:
|
|
15
|
+
- /resources
|
|
16
|
+
- /test-resources
|
|
17
|
+
url: https://ui5.sap.com
|
|
18
|
+
backend:
|
|
19
|
+
- path: /sap
|
|
20
|
+
url: https://172.16.24.98:44300
|
|
21
|
+
client: '100'
|
|
22
|
+
- name: fiori-tools-appreload
|
|
23
|
+
afterMiddleware: compression
|
|
24
|
+
configuration:
|
|
25
|
+
port: 35729
|
|
26
|
+
path: webapp
|
|
27
|
+
delay: 300
|
|
28
|
+
- name: fiori-tools-preview
|
|
29
|
+
afterMiddleware: fiori-tools-appreload
|
|
30
|
+
configuration:
|
|
31
|
+
flp:
|
|
32
|
+
theme: sap_fiori_3
|
|
33
|
+
path: test/flpSandbox.html
|
|
34
|
+
intent:
|
|
35
|
+
object: zlasinspectionlog
|
|
36
|
+
action: tile
|
|
37
|
+
builder:
|
|
38
|
+
resources:
|
|
39
|
+
excludes:
|
|
40
|
+
- /test/**
|
|
41
|
+
- /localService/**
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
7
|
+
<title>Equipment Inspection Logs</title>
|
|
8
|
+
<style>
|
|
9
|
+
html, body, body > div, #container, #container-uiarea {
|
|
10
|
+
height: 100%;
|
|
11
|
+
}
|
|
12
|
+
</style>
|
|
13
|
+
<script
|
|
14
|
+
id="sap-ui-bootstrap"
|
|
15
|
+
src="resources/sap-ui-core.js"
|
|
16
|
+
data-sap-ui-theme="sap_fiori_3"
|
|
17
|
+
data-sap-ui-resource-roots='{
|
|
18
|
+
"zlas.inspectionlog": "./"
|
|
19
|
+
}'
|
|
20
|
+
data-sap-ui-on-init="module:sap/ui/core/ComponentSupport"
|
|
21
|
+
data-sap-ui-compat-version="edge"
|
|
22
|
+
data-sap-ui-async="true"
|
|
23
|
+
data-sap-ui-frame-options="trusted"
|
|
24
|
+
></script>
|
|
25
|
+
</head>
|
|
26
|
+
<body class="sapUiBody sapUiSizeCompact" id="content">
|
|
27
|
+
<div
|
|
28
|
+
data-sap-ui-component
|
|
29
|
+
data-name="zlas.inspectionlog"
|
|
30
|
+
data-id="container"
|
|
31
|
+
data-settings='{"id" : "zlas.inspectionlog"}'
|
|
32
|
+
data-handle-validation="true"
|
|
33
|
+
></div>
|
|
34
|
+
</body>
|
|
35
|
+
</html>
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_version": "1.60.0",
|
|
3
|
+
"sap.app": {
|
|
4
|
+
"id": "zlas.inspectionlog",
|
|
5
|
+
"type": "application",
|
|
6
|
+
"i18n": "i18n/i18n.properties",
|
|
7
|
+
"applicationVersion": {
|
|
8
|
+
"version": "0.0.1"
|
|
9
|
+
},
|
|
10
|
+
"title": "{{appTitle}}",
|
|
11
|
+
"description": "{{appDescription}}",
|
|
12
|
+
"resources": "resources.json",
|
|
13
|
+
"sourceTemplate": {
|
|
14
|
+
"id": "@sap-ux/fiori-elements-writer:lrop",
|
|
15
|
+
"version": "3.0.39"
|
|
16
|
+
},
|
|
17
|
+
"dataSources": {
|
|
18
|
+
"mainService": {
|
|
19
|
+
"uri": "/sap/opu/odata4/sap/zui_las_inspectionlog_o4/srvd/sap/zui_las_inspectionlog_o4/0001/",
|
|
20
|
+
"type": "OData",
|
|
21
|
+
"settings": {
|
|
22
|
+
"annotations": [],
|
|
23
|
+
"odataVersion": "4.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"sap.ui": {
|
|
29
|
+
"technology": "UI5",
|
|
30
|
+
"icons": {
|
|
31
|
+
"icon": "",
|
|
32
|
+
"favIcon": "",
|
|
33
|
+
"phone": "",
|
|
34
|
+
"phone@2": "",
|
|
35
|
+
"tablet": "",
|
|
36
|
+
"tablet@2": ""
|
|
37
|
+
},
|
|
38
|
+
"deviceTypes": {
|
|
39
|
+
"desktop": true,
|
|
40
|
+
"tablet": true,
|
|
41
|
+
"phone": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"sap.ui5": {
|
|
45
|
+
"flexEnabled": true,
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"minUI5Version": "1.120.0",
|
|
48
|
+
"libs": {
|
|
49
|
+
"sap.m": {},
|
|
50
|
+
"sap.ui.core": {},
|
|
51
|
+
"sap.fe.templates": {}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"contentDensities": {
|
|
55
|
+
"compact": true,
|
|
56
|
+
"cozy": true
|
|
57
|
+
},
|
|
58
|
+
"models": {
|
|
59
|
+
"i18n": {
|
|
60
|
+
"type": "sap.ui.model.resource.ResourceModel",
|
|
61
|
+
"settings": {
|
|
62
|
+
"bundleName": "zlas.inspectionlog.i18n.i18n"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"": {
|
|
66
|
+
"dataSource": "mainService",
|
|
67
|
+
"preload": true,
|
|
68
|
+
"settings": {
|
|
69
|
+
"operationMode": "Server",
|
|
70
|
+
"autoExpandSelect": true,
|
|
71
|
+
"earlyRequests": true
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"@i18n": {
|
|
75
|
+
"type": "sap.ui.model.resource.ResourceModel",
|
|
76
|
+
"uri": "i18n/i18n.properties"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"resources": {
|
|
80
|
+
"css": []
|
|
81
|
+
},
|
|
82
|
+
"routing": {
|
|
83
|
+
"config": {},
|
|
84
|
+
"routes": [
|
|
85
|
+
{
|
|
86
|
+
"pattern": ":?query:",
|
|
87
|
+
"name": "InspectionLogList",
|
|
88
|
+
"target": "InspectionLogList"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"pattern": "InspectionLog({key}):?query:",
|
|
92
|
+
"name": "InspectionLogObjectPage",
|
|
93
|
+
"target": "InspectionLogObjectPage"
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"targets": {
|
|
97
|
+
"InspectionLogList": {
|
|
98
|
+
"type": "Component",
|
|
99
|
+
"id": "InspectionLogList",
|
|
100
|
+
"name": "sap.fe.templates.ListReport",
|
|
101
|
+
"options": {
|
|
102
|
+
"settings": {
|
|
103
|
+
"contextPath": "/InspectionLog",
|
|
104
|
+
"variantManagement": "Page",
|
|
105
|
+
"navigation": {
|
|
106
|
+
"InspectionLog": {
|
|
107
|
+
"detail": {
|
|
108
|
+
"route": "InspectionLogObjectPage"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"controlConfiguration": {
|
|
113
|
+
"@com.sap.vocabularies.UI.v1.LineItem": {
|
|
114
|
+
"tableSettings": {
|
|
115
|
+
"type": "ResponsiveTable"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"InspectionLogObjectPage": {
|
|
123
|
+
"type": "Component",
|
|
124
|
+
"id": "InspectionLogObjectPage",
|
|
125
|
+
"name": "sap.fe.templates.ObjectPage",
|
|
126
|
+
"options": {
|
|
127
|
+
"settings": {
|
|
128
|
+
"editableHeaderContent": false,
|
|
129
|
+
"contextPath": "/InspectionLog"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"sap.fiori": {
|
|
137
|
+
"registrationIds": [],
|
|
138
|
+
"archeType": "transactional"
|
|
139
|
+
},
|
|
140
|
+
"sap.fe": {
|
|
141
|
+
"app": {
|
|
142
|
+
"enableLazyLoading": true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|