@stage5/lumine 0.3.48 → 0.3.50
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/lib/admin-build-plans.js +118 -0
- package/lib/admin-owner-trace.js +74 -1
- package/lib/admin.js +13 -0
- package/lib/commands.js +4 -1
- package/package.json +1 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// `lumine admin build-plans stuck|recheck`: Lumine Build plans whose current
|
|
2
|
+
// milestone is not finishing (twinkle-api controllers/cli/admin/buildPlans.ts).
|
|
3
|
+
// stuck lists running plans with their current milestone's runs, Energy vs
|
|
4
|
+
// estimate, gradings and loop-guard state (flagged ones only unless --all).
|
|
5
|
+
// recheck re-grades the current milestone of up to 10 builds with today's
|
|
6
|
+
// evaluator, free for the creator; it is a dry run unless --confirm is given.
|
|
7
|
+
|
|
8
|
+
export const BUILD_PLANS_USAGE =
|
|
9
|
+
"Usage: lumine admin build-plans stuck [--days <1-90>] [--all] | recheck <buildId> [<buildId> ...] [--confirm] [--json]";
|
|
10
|
+
const RECHECK_MAX = 10;
|
|
11
|
+
|
|
12
|
+
function validationError(message) {
|
|
13
|
+
const error = new Error(message);
|
|
14
|
+
error.code = "CLI_ADMIN_CLI_VALIDATION";
|
|
15
|
+
return error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function confirmed(options) {
|
|
19
|
+
const confirm = options.adminConfirm;
|
|
20
|
+
// absent is "", a bare --confirm is "true"
|
|
21
|
+
if (confirm !== "" && confirm !== "true") {
|
|
22
|
+
throw validationError("Pass --confirm on its own, without a value.");
|
|
23
|
+
}
|
|
24
|
+
return confirm === "true";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildBuildPlansOperation({ action, options }) {
|
|
28
|
+
if (!action || action === "stuck") {
|
|
29
|
+
const query = new URLSearchParams();
|
|
30
|
+
if (options.adminDays) {
|
|
31
|
+
const days = Number(options.adminDays);
|
|
32
|
+
if (!Number.isSafeInteger(days) || days < 1 || days > 90) {
|
|
33
|
+
throw validationError("--days must be a whole number from 1 to 90.");
|
|
34
|
+
}
|
|
35
|
+
query.set("days", String(days));
|
|
36
|
+
}
|
|
37
|
+
if (options.adminAll) query.set("all", "1");
|
|
38
|
+
const qs = query.toString();
|
|
39
|
+
return {
|
|
40
|
+
name: "build-plans.stuck",
|
|
41
|
+
method: "GET",
|
|
42
|
+
path: `/cli/admin/build-plans/stuck${qs ? `?${qs}` : ""}`,
|
|
43
|
+
body: undefined,
|
|
44
|
+
mutates: false,
|
|
45
|
+
requiresRun: false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (action === "recheck") {
|
|
49
|
+
const ids = (options.positional || [])
|
|
50
|
+
.slice(2)
|
|
51
|
+
.map((value) => String(value).split(","))
|
|
52
|
+
.flat()
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.map((value) => Number(value));
|
|
55
|
+
if (
|
|
56
|
+
!ids.length ||
|
|
57
|
+
ids.length > RECHECK_MAX ||
|
|
58
|
+
ids.some((id) => !Number.isSafeInteger(id) || id <= 0)
|
|
59
|
+
) {
|
|
60
|
+
throw validationError(`Give 1 to ${RECHECK_MAX} build ids. ${BUILD_PLANS_USAGE}`);
|
|
61
|
+
}
|
|
62
|
+
const confirm = confirmed(options);
|
|
63
|
+
return {
|
|
64
|
+
name: confirm ? "build-plans.recheck" : "build-plans.recheck.preview",
|
|
65
|
+
method: "POST",
|
|
66
|
+
path: "/cli/admin/build-plans/recheck",
|
|
67
|
+
body: { buildIds: [...new Set(ids)], dryRun: !confirm },
|
|
68
|
+
mutates: confirm,
|
|
69
|
+
requiresRun: false,
|
|
70
|
+
// One grading per build, one at a time, each up to a few minutes.
|
|
71
|
+
timeoutMs: 15 * 60 * 1000,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
throw validationError(BUILD_PLANS_USAGE);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function formatRow(row) {
|
|
78
|
+
const ratio = row.energyRatio == null ? "?" : `${row.energyRatio}x`;
|
|
79
|
+
const milestone =
|
|
80
|
+
row.milestoneIndex == null ? "-" : `${row.milestoneIndex + 1}/${row.milestones}`;
|
|
81
|
+
const coverage = row.coverage
|
|
82
|
+
? ` files ${row.coverage.shown}/${row.coverage.total}${row.coverage.toolRounds ? ` +${row.coverage.toolFilesRead} read in ${row.coverage.toolRounds} rounds` : ""}${row.coverage.omittedReferenced ? `, ${row.coverage.omittedReferenced} referenced omitted` : ""}`
|
|
83
|
+
: "";
|
|
84
|
+
const state = row.stalled
|
|
85
|
+
? ` STALLED(${row.stallReason})`
|
|
86
|
+
: row.wouldStall
|
|
87
|
+
? ` would stall (${row.wouldStall})`
|
|
88
|
+
: "";
|
|
89
|
+
return `Build ${row.buildId}: milestone ${milestone} (${row.milestonesDone} done), ${row.runs} runs, Energy ${ratio} of estimate, ${row.evaluations ?? "?"} gradings, non-pass streak ${row.consecutiveNonPass ?? "?"}, ${row.passed}/${row.criteria} passing, ${row.unverified} unverified${state};${coverage}${row.flags?.length ? ` [${row.flags.join(", ")}]` : ""}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function formatBuildPlansResult({ operation, data }) {
|
|
93
|
+
if (operation.name === "build-plans.stuck") {
|
|
94
|
+
const rows = data?.rows || [];
|
|
95
|
+
return [
|
|
96
|
+
`${data?.running ?? 0} running build plan(s); ${data?.inWindow ?? rows.length} updated in the last ${data?.days} days or stalled (${data?.stalled ?? 0} stalled); ${rows.length} shown.`,
|
|
97
|
+
...rows.map(formatRow),
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
const lines = [];
|
|
101
|
+
for (const error of data?.errors || []) {
|
|
102
|
+
lines.push(`Skipped "${error.value}": ${error.reason === "duplicate" ? "listed twice" : "not a build id"}`);
|
|
103
|
+
}
|
|
104
|
+
if (data?.dryRun) {
|
|
105
|
+
lines.push(`Dry run: would recheck ${data?.buildIds?.length || 0} build(s).`);
|
|
106
|
+
for (const row of data?.rows || []) lines.push(formatRow(row));
|
|
107
|
+
lines.push("Nothing was graded. Add --confirm to recheck them (free for the creators).");
|
|
108
|
+
return lines;
|
|
109
|
+
}
|
|
110
|
+
for (const result of data?.results || []) {
|
|
111
|
+
lines.push(
|
|
112
|
+
result?.ok
|
|
113
|
+
? `${result.replayed ? "(already rechecked by this request) " : ""}Build ${result.buildId}: milestone ${result.milestoneIndex + 1} ${result.verdict}, ${result.passed}/${result.criteria} passing, ${result.unverified} unverified${result.milestoneCompleted ? ", milestone completed" : ""}${result.stalled ? `, stalled (${result.stalled.reason})` : ""}${result.fileCoverage ? `; files ${result.fileCoverage.shownFiles}/${result.fileCoverage.totalFiles} + ${result.fileCoverage.toolFilesRead || 0} read in ${result.fileCoverage.toolRounds || 0} rounds` : ""}${result.usage ? `; cost ${result.usage.energyUnits} Energy units (${result.usage.inputTokens} in / ${result.usage.outputTokens} out tokens), not charged to the creator` : ""}`
|
|
114
|
+
: `Build ${result?.buildId}: not rechecked (${result?.reason === "run_in_progress" ? "a Lumine run is in progress; try again later" : result?.reason || "unknown"})`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
return lines;
|
|
118
|
+
}
|
package/lib/admin-owner-trace.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// no daily run needed. The server keeps 30 days.
|
|
5
5
|
|
|
6
6
|
export const OWNER_TRACE_USAGE =
|
|
7
|
-
"Usage: lumine admin owner-trace [--since 2h|30m|3d|<iso>|<unix>] [--type route,feed-open,scroll-anchor,content-scroll,error,...] [--perf] [--path <substring>] [--limit 300] [--json].";
|
|
7
|
+
"Usage: lumine admin owner-trace [--since 2h|30m|3d|<iso>|<unix>] [--type route,feed-open,scroll-anchor,content-scroll,error,...] [--perf] [--wake] [--path <substring>] [--limit 300] [--json].";
|
|
8
8
|
|
|
9
9
|
// --perf: only the performance events (tap-to-page timeline, slow API
|
|
10
10
|
// requests, main-thread stalls, page load).
|
|
@@ -15,8 +15,14 @@ export const OWNER_TRACE_PERF_TYPES = [
|
|
|
15
15
|
"page-load",
|
|
16
16
|
"scroll-perf",
|
|
17
17
|
"scroll-frame",
|
|
18
|
+
"wake",
|
|
18
19
|
];
|
|
19
20
|
|
|
21
|
+
// --wake: the session headers and the wake profiles (what the site did in the
|
|
22
|
+
// 20 s after each load or return to the tab), to compare Wi-Fi and cellular
|
|
23
|
+
// wakes (the server tags both with the network).
|
|
24
|
+
export const OWNER_TRACE_WAKE_TYPES = ["session", "wake"];
|
|
25
|
+
|
|
20
26
|
const MAX_LIMIT = 2000;
|
|
21
27
|
|
|
22
28
|
function validationError(message) {
|
|
@@ -61,6 +67,11 @@ export function buildOwnerTraceOperation(options) {
|
|
|
61
67
|
if (!types.includes(type)) types.push(type);
|
|
62
68
|
}
|
|
63
69
|
}
|
|
70
|
+
if (options.adminWake) {
|
|
71
|
+
for (const type of OWNER_TRACE_WAKE_TYPES) {
|
|
72
|
+
if (!types.includes(type)) types.push(type);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
64
75
|
if (types.length) query.set("type", types.join(","));
|
|
65
76
|
if (options.adminPath) query.set("path", String(options.adminPath));
|
|
66
77
|
return {
|
|
@@ -103,6 +114,12 @@ function describeSession(data = {}, path = "") {
|
|
|
103
114
|
data.mobile ? "mobile" : "",
|
|
104
115
|
data.standalone ? "standalone (PWA)" : "browser tab",
|
|
105
116
|
data.touch ? `touch ${data.touch}` : "",
|
|
117
|
+
data.navType === "back_forward"
|
|
118
|
+
? "restored (iOS reloaded the discarded tab)"
|
|
119
|
+
: data.navType
|
|
120
|
+
? `nav ${data.navType}`
|
|
121
|
+
: "",
|
|
122
|
+
describeNet(data.net),
|
|
106
123
|
].filter(Boolean);
|
|
107
124
|
if (path) parts.push(`at ${path}`);
|
|
108
125
|
return `${parts.join(" · ")}${data.ua ? `\n ua: ${data.ua}` : ""}`;
|
|
@@ -140,6 +157,8 @@ export function describeOwnerTraceEvent(event) {
|
|
|
140
157
|
return describeScrollPerf(data);
|
|
141
158
|
case "scroll-frame":
|
|
142
159
|
return describeScrollFrame(data);
|
|
160
|
+
case "wake":
|
|
161
|
+
return describeWake(data);
|
|
143
162
|
case "stall":
|
|
144
163
|
return `main thread blocked ${data.ms}ms${data.n > 1 ? ` (${data.n} stalls, ${data.total}ms blocked over ${data.span}ms)` : ""}`;
|
|
145
164
|
case "page-load": {
|
|
@@ -160,6 +179,60 @@ export function describeOwnerTraceEvent(event) {
|
|
|
160
179
|
}
|
|
161
180
|
}
|
|
162
181
|
|
|
182
|
+
// The network an upload came from: v4/v6 plus an opaque prefix hash. The same
|
|
183
|
+
// tag = the same network (home Wi-Fi vs the carrier).
|
|
184
|
+
function describeNet(net) {
|
|
185
|
+
if (!net?.id) return "";
|
|
186
|
+
return `net v${net.v}:${net.id}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// What the site did in the window after a load or a return to the tab.
|
|
190
|
+
// A field the server cut to "[cut N chars]" arrives as a string: every list or
|
|
191
|
+
// object is checked before use, so one trimmed row never breaks the output.
|
|
192
|
+
function describeWake(data) {
|
|
193
|
+
const head =
|
|
194
|
+
data.kind === "load"
|
|
195
|
+
? `LOAD${data.navType === "back_forward" ? " (iOS restore)" : data.navType ? ` (${data.navType})` : ""}`
|
|
196
|
+
: data.kind === "resume"
|
|
197
|
+
? `RESUME after ${formatDuration(data.awayMs)}`
|
|
198
|
+
: "WAKE (kind unknown)";
|
|
199
|
+
const socket = isPlainObject(data.socket) ? data.socket : {};
|
|
200
|
+
const reasons = Array.isArray(socket.reasons) ? socket.reasons : [];
|
|
201
|
+
const top = Array.isArray(data.top) ? data.top : [];
|
|
202
|
+
const work = Object.entries(isPlainObject(data.work) ? data.work : {})
|
|
203
|
+
.map(([name, n]) => `${name}${n > 1 ? ` x${n}` : ""}`)
|
|
204
|
+
.join(", ");
|
|
205
|
+
const parts = [
|
|
206
|
+
head,
|
|
207
|
+
describeNet(data.net),
|
|
208
|
+
`${formatDuration(data.windowMs)}${data.end && data.end !== "window" ? ` (ended: ${data.end})` : ""}`,
|
|
209
|
+
`frames ${data.frames ?? "?"}, ${data.over50 ?? 0} >50ms, ${data.over100 ?? 0} >100ms, max ${data.maxFrameMs ?? "?"}ms, jank ${data.jankMs ?? 0}ms`,
|
|
210
|
+
data.stalls ? `${data.stalls} stall(s) ${data.stallMs}ms` : "",
|
|
211
|
+
`${data.requests ?? 0} requests${data.repeats ? ` (${data.repeats} repeats)` : ""}`,
|
|
212
|
+
data.images ? `${data.images} images` : "",
|
|
213
|
+
`socket ${socket.atStart === true ? "up" : socket.atStart === false ? "down" : "?"} at start, ${socket.connects ?? 0} connect(s), ${socket.disconnects ?? 0} disconnect(s)${socket.attempts ? `, ${socket.attempts} attempt(s)` : ""}${reasons.length ? ` [${reasons.join(", ")}]` : ""}`,
|
|
214
|
+
work ? `work ${work}` : "",
|
|
215
|
+
top.length
|
|
216
|
+
? `top ${top.join(", ")}${data.topCut ? ` +${data.topCut} more` : ""}`
|
|
217
|
+
: "",
|
|
218
|
+
data._trimmed ? `trimmed ${JSON.stringify(data._trimmed)}` : "",
|
|
219
|
+
].filter(Boolean);
|
|
220
|
+
return parts.join(" · ");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function isPlainObject(value) {
|
|
224
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function formatDuration(ms) {
|
|
228
|
+
const n = Number(ms);
|
|
229
|
+
if (!Number.isFinite(n)) return "?";
|
|
230
|
+
if (n < 1000) return `${Math.round(n)}ms`;
|
|
231
|
+
if (n < 120_000) return `${Math.round(n / 100) / 10}s`;
|
|
232
|
+
if (n < 7_200_000) return `${Math.round(n / 60_000)}m`;
|
|
233
|
+
return `${Math.round(n / 360_000) / 10}h`;
|
|
234
|
+
}
|
|
235
|
+
|
|
163
236
|
// One Home/page scroll burst: frame pacing plus what was on screen.
|
|
164
237
|
function describeScrollPerf(data) {
|
|
165
238
|
const cards = (data.context?.cards || [])
|
package/lib/admin.js
CHANGED
|
@@ -49,6 +49,10 @@ import {
|
|
|
49
49
|
buildSlidingBlockOperation,
|
|
50
50
|
formatSlidingBlockResult,
|
|
51
51
|
} from "./admin-sliding-block.js";
|
|
52
|
+
import {
|
|
53
|
+
buildBuildPlansOperation,
|
|
54
|
+
formatBuildPlansResult,
|
|
55
|
+
} from "./admin-build-plans.js";
|
|
52
56
|
import { formatChatReportResult } from "./admin-chat-reports.js";
|
|
53
57
|
import { formatIdentityDeviceUsage } from "./admin-identity-inspection.js";
|
|
54
58
|
import {
|
|
@@ -2646,6 +2650,10 @@ export function parseAdminOperation(options) {
|
|
|
2646
2650
|
if (namespace === "sliding-block") {
|
|
2647
2651
|
return buildSlidingBlockOperation({ action, options });
|
|
2648
2652
|
}
|
|
2653
|
+
// Stuck Lumine Build plans and their free recheck (admin-build-plans.js).
|
|
2654
|
+
if (namespace === "build-plans") {
|
|
2655
|
+
return buildBuildPlansOperation({ action, options });
|
|
2656
|
+
}
|
|
2649
2657
|
|
|
2650
2658
|
if (namespace === "correction" || namespace === "corrections") {
|
|
2651
2659
|
if (!action || action === "status") {
|
|
@@ -7389,6 +7397,11 @@ function printAdminResult({ operation, result }) {
|
|
|
7389
7397
|
console.log(line);
|
|
7390
7398
|
return;
|
|
7391
7399
|
}
|
|
7400
|
+
if (operation.name.startsWith("build-plans.")) {
|
|
7401
|
+
for (const line of formatBuildPlansResult({ operation, data }))
|
|
7402
|
+
console.log(line);
|
|
7403
|
+
return;
|
|
7404
|
+
}
|
|
7392
7405
|
if (operation.name.startsWith("craft.")) {
|
|
7393
7406
|
const lines = formatCraftResult({ operation, data });
|
|
7394
7407
|
if (lines) {
|
package/lib/commands.js
CHANGED
|
@@ -2469,6 +2469,7 @@ export function parseArgs(args) {
|
|
|
2469
2469
|
"resume",
|
|
2470
2470
|
"sinceRun",
|
|
2471
2471
|
"perf",
|
|
2472
|
+
"wake",
|
|
2472
2473
|
"includeLegacy",
|
|
2473
2474
|
"includePrivateEvidence",
|
|
2474
2475
|
"allFlagged",
|
|
@@ -2639,6 +2640,7 @@ export function parseArgs(args) {
|
|
|
2639
2640
|
adminSince: raw.since ? String(raw.since) : "",
|
|
2640
2641
|
adminPath: raw.path ? String(raw.path) : "",
|
|
2641
2642
|
adminPerf: Boolean(raw.perf),
|
|
2643
|
+
adminWake: Boolean(raw.wake),
|
|
2642
2644
|
adminMinLevel: raw.minLevel === undefined ? null : raw.minLevel,
|
|
2643
2645
|
adminIncludeLegacy: Boolean(raw.includeLegacy),
|
|
2644
2646
|
adminIncludePrivateEvidence: parseBoolean(
|
|
@@ -3261,6 +3263,7 @@ export function printHelp() {
|
|
|
3261
3263
|
lumine admin cardcraft-review list [--status pending|approved|all] [--cursor <id>] | show <id> | approve|reject|revoke <id> [--reason <text>]
|
|
3262
3264
|
lumine admin craft seed --build <id> --file <pairs.json> [--confirm] | stats --build <id> [--limit <1-100>] | revise --build <id> --item <id|name> [--why <text>] [--emoji <emoji>] [--category <field>] [--confirm] | revise --build <id> --pair <pairId|a+b> --why <text> [--confirm] | retire --build <id> --item <id|name> --reason <text> [--confirm] | unfizzle --build <id> [--since <iso>] [--pair <a+b>] [--confirm] (Twinkle.craft curation: dry run without --confirm)
|
|
3263
3265
|
lumine admin sliding-block status | add --file <levels.json> [--confirm] (sliding-block-v1 campaign refills, RushHour: dry run without --confirm; the server solves each board and appends it)
|
|
3266
|
+
lumine admin build-plans stuck [--days <1-90>] [--all] | recheck <buildId> [<buildId> ...] [--confirm] (Lumine Build plans stuck on one milestone: runs, Energy vs estimate, gradings, loop-guard state; recheck re-grades the current milestone with today's evaluator, free for the creator, dry run without --confirm)
|
|
3264
3267
|
lumine admin storage list [--status pending|approved|rejected|all] [--cursor <id>] | show <user-id-or-username> | approve <request-id> [--size <500MB|1GB|2GB|n MB>] [--reason <text>] | reject <request-id> [--reason <text>] | grant <user-id-or-username> --size <500MB|1GB|2GB|n MB> [--reason <text>]
|
|
3265
3268
|
lumine admin meetup list [--status review|active|completed|all] | show <crewId> | info <crewId> <userId> --decision ask|accept|ask-again|withdraw [--note <text>] | set-branch <crewId> <userId> --branch "<name>" [--note <text>] | branches [list [--status pending|official|rejected|all] | show <id> | approve <id> | reject <id> [--note <text>] | merge <id> --into <officialId>] | emails [list|send <id>|discard <id>|review on|off] | friends [status|on|off] | funnel [--days <1-90>] | announce-checkins [--send] [--as zero|ciel] (Bridge Builder crews and check-ins)
|
|
3266
3269
|
lumine admin meetup story list | show <crewId> | approve <crewId> [--note <text>] [--no-announce] | send-back <crewId> --note <text> | unpublish <crewId> --note <text> (Bridge Builder story pages: publishes only what every tagged member's parent allowed)
|
|
@@ -3311,7 +3314,7 @@ export function printHelp() {
|
|
|
3311
3314
|
lumine admin reward-bank coverage <buildId> [--max-step 3] [--json]
|
|
3312
3315
|
lumine admin reward-bank seed <buildId> [--max-step 3] [--identity zero|ciel|auto] [--json] (writes ONE missing seed question, lowest step first, billed to Zero/Ciel's AI Energy; inside a daily run or on its own, owner only)
|
|
3313
3316
|
lumine admin reward-bank report <buildId> [--days 1] [--json] (question-bank telemetry: generation outcomes, learners, payouts, ladder, questions to review)
|
|
3314
|
-
lumine admin owner-trace [--since 2h|30m|3d|<iso>] [--type route,feed-open,scroll-anchor,content-scroll,error,rejection,session,nav-timing,slow-request,stall,page-load,scroll-perf,scroll-frame] [--perf] [--path <substring>] [--limit 300] [--json] (what the website did on Mikey's own devices: routes with PUSH/POP/REPLACE, Home feed card opens, scroll-anchor restores, content scroll positions, window errors, and performance: tap-to-page timing, API requests over 1 s, main-thread stalls over 200 ms, page load, scroll frame pacing and slow-frame attribution (Long Animation Frame scripts on Chrome, feed card renders on Safari); --perf shows only those; kept 30 days)
|
|
3317
|
+
lumine admin owner-trace [--since 2h|30m|3d|<iso>] [--type route,feed-open,scroll-anchor,content-scroll,error,rejection,session,nav-timing,slow-request,stall,page-load,scroll-perf,scroll-frame] [--perf] [--wake] [--path <substring>] [--limit 300] [--json] (--wake: only sessions and the 20 s wake profiles after each load or return, with the network tag that separates Wi-Fi from cellular; what the website did on Mikey's own devices: routes with PUSH/POP/REPLACE, Home feed card opens, scroll-anchor restores, content scroll positions, window errors, and performance: tap-to-page timing, API requests over 1 s, main-thread stalls over 200 ms, page load, scroll frame pacing and slow-frame attribution (Long Animation Frame scripts on Chrome, feed card renders on Safari); --perf shows only those; kept 30 days)
|
|
3315
3318
|
lumine admin roles [--min-level 1] [--limit 50] [--json] (every user type with its management level and who holds it; admin pages need level 3, achievement points never pass level 2)
|
|
3316
3319
|
lumine admin comment draft <target> [--type subject|comment|build|aiStory|dailyReflection] [--file <comment.md>] [--review-receipt <review.json>|--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
|
|
3317
3320
|
lumine admin comment reply comment:<id> [--file <reply.md>] [--reviewed-version <id> --reviewed-via runtime|code] [--review-context <context.json>] [--identity zero|ciel|auto] [--json]
|