@codyswann/lisa 4.18.0 → 4.20.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/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +14 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +24 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/copy-overwrite/scripts/bdd/baseline.mjs +10 -2
- package/expo/copy-overwrite/scripts/bdd/render.mjs +3 -2
- package/expo/copy-overwrite/scripts/bdd-matrix.mjs +3 -3
- package/package.json +7 -2
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-secrets-access/scripts/publish-aws-bootstrap.mjs +349 -0
- package/plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md +23 -5
- package/scripts/check-ui-demo-data.mjs +102 -0
- package/ui/README.md +32 -0
- package/ui/index.html +901 -115
package/ui/index.html
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
+
<!-- DESIGN-SOURCE: none — not in Figma -->
|
|
2
3
|
<html lang="en">
|
|
3
4
|
<head>
|
|
4
5
|
<meta charset="utf-8" />
|
|
@@ -1856,6 +1857,7 @@
|
|
|
1856
1857
|
* control: { type: 'toggle'|'select'|'text'|'number'|'tags'|'envmap'|'static', ... }
|
|
1857
1858
|
* ------------------------------------------------------------------ */
|
|
1858
1859
|
|
|
1860
|
+
/* LISA_UI_CATALOG_START */
|
|
1859
1861
|
const sel = (value, options) => ({ type: "select", value, options });
|
|
1860
1862
|
const tog = value => ({ type: "toggle", value });
|
|
1861
1863
|
const txt = (value, opts) =>
|
|
@@ -1870,6 +1872,68 @@
|
|
|
1870
1872
|
});
|
|
1871
1873
|
const stat = (value, cls) => ({ type: "static", value, cls });
|
|
1872
1874
|
|
|
1875
|
+
/**
|
|
1876
|
+
* Build one live control from a present value without retaining demo
|
|
1877
|
+
* sub-values. Returning null makes a malformed live value render unknown
|
|
1878
|
+
* instead of preserving whichever plausible value the prototype carried.
|
|
1879
|
+
* Each renderer validates its input before coercion: JavaScript truthiness
|
|
1880
|
+
* and object stringification turn malformed configuration into convincing
|
|
1881
|
+
* lies such as `"false"` → on and `{}` → `[object Object]`.
|
|
1882
|
+
*/
|
|
1883
|
+
function hydrateControl(control, value) {
|
|
1884
|
+
if (control.type === "toggle" && typeof value === "boolean") {
|
|
1885
|
+
return { ...control, value };
|
|
1886
|
+
}
|
|
1887
|
+
if (
|
|
1888
|
+
control.type === "number" &&
|
|
1889
|
+
typeof value === "number" &&
|
|
1890
|
+
Number.isFinite(value)
|
|
1891
|
+
) {
|
|
1892
|
+
return { ...control, value };
|
|
1893
|
+
}
|
|
1894
|
+
if (
|
|
1895
|
+
(control.type === "text" || control.type === "select") &&
|
|
1896
|
+
typeof value === "string"
|
|
1897
|
+
) {
|
|
1898
|
+
return { ...control, value };
|
|
1899
|
+
}
|
|
1900
|
+
if (
|
|
1901
|
+
control.type === "tags" &&
|
|
1902
|
+
Array.isArray(value) &&
|
|
1903
|
+
value.every(item => typeof item === "string")
|
|
1904
|
+
) {
|
|
1905
|
+
return { ...control, values: value };
|
|
1906
|
+
}
|
|
1907
|
+
if (control.type === "envmap") {
|
|
1908
|
+
const hydrated = {
|
|
1909
|
+
...control,
|
|
1910
|
+
dev: "unknown",
|
|
1911
|
+
staging: "unknown",
|
|
1912
|
+
production: "unknown",
|
|
1913
|
+
};
|
|
1914
|
+
if (typeof value === "string") {
|
|
1915
|
+
hydrated.production = value;
|
|
1916
|
+
return hydrated;
|
|
1917
|
+
}
|
|
1918
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1919
|
+
["dev", "staging", "production"].forEach(envName => {
|
|
1920
|
+
if (
|
|
1921
|
+
Object.hasOwn(value, envName) &&
|
|
1922
|
+
typeof value[envName] === "string"
|
|
1923
|
+
) {
|
|
1924
|
+
hydrated[envName] = value[envName];
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
return hydrated;
|
|
1928
|
+
}
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
if (control.type === "static" && typeof value === "string") {
|
|
1932
|
+
return { ...control, value };
|
|
1933
|
+
}
|
|
1934
|
+
return null;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1873
1937
|
const SEVERITIES = ["error", "warn", "off"];
|
|
1874
1938
|
|
|
1875
1939
|
const DATA = {
|
|
@@ -1958,6 +2022,7 @@
|
|
|
1958
2022
|
},
|
|
1959
2023
|
{
|
|
1960
2024
|
type: "callout",
|
|
2025
|
+
staticCopy: "fixed Lisa factory and gate model",
|
|
1961
2026
|
text: "<b>The gates are adversarial.</b> Agents, humans, and automations submit inputs; an intake agent admits only work that is unambiguous and that the factory has provable tooling access to execute — everything else is rejected and raised. Three loops feed the pipeline on schedule: <b>QA</b> (exploratory bugs), <b>Product Planning</b> (ideated PRDs), and <b>Monitoring</b> (observability regressions). Verify failures flow straight back into Implement as build-ready tickets. The goal: <b>non-technical people creating scalable software</b> — everything that crosses a gate is written for them, and end users never talk to a coding agent.",
|
|
1962
2027
|
},
|
|
1963
2028
|
{
|
|
@@ -2013,6 +2078,7 @@
|
|
|
2013
2078
|
rows: [
|
|
2014
2079
|
{
|
|
2015
2080
|
key: "",
|
|
2081
|
+
staticCopy: "fixed Lisa configuration precedence",
|
|
2016
2082
|
label: "Resolution order",
|
|
2017
2083
|
desc: "Skill arguments always win, then the per-developer local file, then the committed file, then Lisa’s built-in default.",
|
|
2018
2084
|
control: stat(
|
|
@@ -2022,6 +2088,7 @@
|
|
|
2022
2088
|
},
|
|
2023
2089
|
{
|
|
2024
2090
|
key: "",
|
|
2091
|
+
staticCopy: "fixed Lisa secret-handling contract",
|
|
2025
2092
|
label: "Secrets",
|
|
2026
2093
|
desc: "Tokens never live in config — only IDs, keys and slugs. Secrets stay in env vars or the OS keychain.",
|
|
2027
2094
|
control: stat(
|
|
@@ -2362,8 +2429,12 @@
|
|
|
2362
2429
|
};
|
|
2363
2430
|
}
|
|
2364
2431
|
|
|
2365
|
-
|
|
2366
|
-
|
|
2432
|
+
/**
|
|
2433
|
+
* Attach provenance where each factory legend is authored so a
|
|
2434
|
+
* project-dependent claim cannot inherit a plausible static source.
|
|
2435
|
+
*/
|
|
2436
|
+
function fLegend(text, provenance) {
|
|
2437
|
+
return { type: "callout", text: text, ...provenance };
|
|
2367
2438
|
}
|
|
2368
2439
|
|
|
2369
2440
|
/* ===== Staff: which coding agent runs this factory =====================
|
|
@@ -2747,7 +2818,8 @@
|
|
|
2747
2818
|
],
|
|
2748
2819
|
},
|
|
2749
2820
|
fLegend(
|
|
2750
|
-
'<b>Without a context source, the PRDs are guesses.</b> The factory can reach the code but not the project\'s accumulated knowledge, so it re-derives context every run and cannot know what was already decided and why. Connect a wiki with <span class="mono">/lisa:agent-ready</span>, or point this factory at another knowledge source. Switching the PRD destination above changes where every PRD this factory produces is filed — the lifecycle roles for the new provider are configured on the <b>PRD source</b> page.'
|
|
2821
|
+
'<b>Without a context source, the PRDs are guesses.</b> The factory can reach the code but not the project\'s accumulated knowledge, so it re-derives context every run and cannot know what was already decided and why. Connect a wiki with <span class="mono">/lisa:agent-ready</span>, or point this factory at another knowledge source. Switching the PRD destination above changes where every PRD this factory produces is filed — the lifecycle roles for the new provider are configured on the <b>PRD source</b> page.',
|
|
2822
|
+
{ demoOnly: true }
|
|
2751
2823
|
),
|
|
2752
2824
|
],
|
|
2753
2825
|
};
|
|
@@ -3099,7 +3171,8 @@
|
|
|
3099
3171
|
],
|
|
3100
3172
|
},
|
|
3101
3173
|
fLegend(
|
|
3102
|
-
"<b>The failure nobody sees is a requirement that never became a ticket.</b> Every other gate here checks the quality of the work items that exist; none of them notices one that is missing. Coverage — every PRD requirement traceable to at least one work item — is the only check that catches it, and it currently runs as an advisory audit rather than a gate."
|
|
3174
|
+
"<b>The failure nobody sees is a requirement that never became a ticket.</b> Every other gate here checks the quality of the work items that exist; none of them notices one that is missing. Coverage — every PRD requirement traceable to at least one work item — is the only check that catches it, and it currently runs as an advisory audit rather than a gate.",
|
|
3175
|
+
{ demoOnly: true }
|
|
3103
3176
|
),
|
|
3104
3177
|
],
|
|
3105
3178
|
};
|
|
@@ -3962,7 +4035,8 @@
|
|
|
3962
4035
|
],
|
|
3963
4036
|
},
|
|
3964
4037
|
fLegend(
|
|
3965
|
-
'<b>The CI column is the one that gates.</b> Agent-hook and git-hook are fast-feedback mirrors a worker can skip; whether an obligation is actually unskippable is inside its CI cell — click it and read <i>Required to merge</i>. Three rows fall short: mutation testing runs in CI but nightly and unrequired, and infrastructure scanning and contract testing have no provider selected at all. Those are the honest gaps — each is one choice away from being a real gate. The <span class="mono">n/a</span> rows are different: those gates need a deployed web or API surface, and this project is a CLI, so they do not apply here rather than being gaps — they become real gates on a frontend or service stack. The agent-hook and git-hook mirrors must be generated from the same config as CI, or they drift and people learn to distrust them.'
|
|
4038
|
+
'<b>The CI column is the one that gates.</b> Agent-hook and git-hook are fast-feedback mirrors a worker can skip; whether an obligation is actually unskippable is inside its CI cell — click it and read <i>Required to merge</i>. Three rows fall short: mutation testing runs in CI but nightly and unrequired, and infrastructure scanning and contract testing have no provider selected at all. Those are the honest gaps — each is one choice away from being a real gate. The <span class="mono">n/a</span> rows are different: those gates need a deployed web or API surface, and this project is a CLI, so they do not apply here rather than being gaps — they become real gates on a frontend or service stack. The agent-hook and git-hook mirrors must be generated from the same config as CI, or they drift and people learn to distrust them.',
|
|
4039
|
+
{ demoOnly: true }
|
|
3966
4040
|
),
|
|
3967
4041
|
],
|
|
3968
4042
|
};
|
|
@@ -4310,7 +4384,8 @@
|
|
|
4310
4384
|
],
|
|
4311
4385
|
},
|
|
4312
4386
|
fLegend(
|
|
4313
|
-
"<b>Verify and the QA loop are the same competence in two positions.</b> Verify drives the product to produce a bounded go/no-go on one work item at a gate; the QA loop drives it continuously to produce unbounded exploratory signal. Both re-enter the pipeline through intake — a finding from either gets no privileged path around the adversarial gate. And neither is a scanner: an automated check that inspects a diff or a running app is an Implement gate, not UAT."
|
|
4387
|
+
"<b>Verify and the QA loop are the same competence in two positions.</b> Verify drives the product to produce a bounded go/no-go on one work item at a gate; the QA loop drives it continuously to produce unbounded exploratory signal. Both re-enter the pipeline through intake — a finding from either gets no privileged path around the adversarial gate. And neither is a scanner: an automated check that inspects a diff or a running app is an Implement gate, not UAT.",
|
|
4388
|
+
{ staticCopy: "fixed Lisa verification and QA roles" }
|
|
4314
4389
|
),
|
|
4315
4390
|
],
|
|
4316
4391
|
};
|
|
@@ -4532,7 +4607,8 @@
|
|
|
4532
4607
|
],
|
|
4533
4608
|
},
|
|
4534
4609
|
fLegend(
|
|
4535
|
-
"<b>Factories fail loudly; sensors fail silently.</b> A factory that cannot run emits a recovery-required outcome. A dead sensor emits nothing — which is pixel-identical to a healthy system with no problems. So a sensor needs a liveness signal independent of its findings: “no bugs this week” and “the bug pipe is broken” must never render the same. The disagreement between sensors is itself a signal — a crash a customer reported that never appeared in errors means the instrumentation has a hole; a spike no customer felt means you are alerting on something users do not."
|
|
4610
|
+
"<b>Factories fail loudly; sensors fail silently.</b> A factory that cannot run emits a recovery-required outcome. A dead sensor emits nothing — which is pixel-identical to a healthy system with no problems. So a sensor needs a liveness signal independent of its findings: “no bugs this week” and “the bug pipe is broken” must never render the same. The disagreement between sensors is itself a signal — a crash a customer reported that never appeared in errors means the instrumentation has a hole; a spike no customer felt means you are alerting on something users do not.",
|
|
4611
|
+
{ staticCopy: "fixed Lisa sensing model" }
|
|
4536
4612
|
),
|
|
4537
4613
|
],
|
|
4538
4614
|
};
|
|
@@ -4832,7 +4908,8 @@
|
|
|
4832
4908
|
],
|
|
4833
4909
|
},
|
|
4834
4910
|
fLegend(
|
|
4835
|
-
'<b>The agent has its own identity by default — everywhere, including your workstation.</b> Your laptop is not a privileged environment; it is just another runner with better latency and worse uptime. When the agent authenticates as itself on every surface, a local pass genuinely predicts a headless pass, because it is the same identity taking the same path. The <span class="mono">yours</span> row above is the exception that proves it: borrowing your session is exactly why that connection works at your desk and nowhere else. Where a surface can federate (CI, via OIDC) it should — keyless beats hidden. The remote-headless static token is the structural weak link, so scope it narrowly and rotate it often; a short-lived credential is worth more than a well-hidden one.'
|
|
4911
|
+
'<b>The agent has its own identity by default — everywhere, including your workstation.</b> Your laptop is not a privileged environment; it is just another runner with better latency and worse uptime. When the agent authenticates as itself on every surface, a local pass genuinely predicts a headless pass, because it is the same identity taking the same path. The <span class="mono">yours</span> row above is the exception that proves it: borrowing your session is exactly why that connection works at your desk and nowhere else. Where a surface can federate (CI, via OIDC) it should — keyless beats hidden. The remote-headless static token is the structural weak link, so scope it narrowly and rotate it often; a short-lived credential is worth more than a well-hidden one.',
|
|
4912
|
+
{ demoOnly: true }
|
|
4836
4913
|
),
|
|
4837
4914
|
],
|
|
4838
4915
|
};
|
|
@@ -5118,7 +5195,8 @@
|
|
|
5118
5195
|
fStaffPicker(),
|
|
5119
5196
|
{ type: "environments" },
|
|
5120
5197
|
fLegend(
|
|
5121
|
-
"<b>Remote is not one place, and local is not manual-only.</b> Every agent ships its own hosted runtime with its own credential store and its own scheduler, so “does this work headless?” has a different answer per agent — which is why this page and the factory pages share one staff selector. Every environment can be scheduled; what differs is durability and granularity. A hosted scheduler survives a closed laptop but imposes a floor on how often it can fire — <b>which is why a loop specified at every 10 minutes cannot run as a Claude cloud routine at all</b>, and has to live locally or in CI. On credentials the ranking is: CI is strongest because it can federate and hold no secret at all; local is next because a human is nearby; the vendor remotes are weakest, since they have neither a human nor federation and fall back to a long-lived token. Scope those narrowly, rotate them often, and put a gateway in front of them — a short-lived credential is worth more than a well-hidden one."
|
|
5198
|
+
"<b>Remote is not one place, and local is not manual-only.</b> Every agent ships its own hosted runtime with its own credential store and its own scheduler, so “does this work headless?” has a different answer per agent — which is why this page and the factory pages share one staff selector. Every environment can be scheduled; what differs is durability and granularity. A hosted scheduler survives a closed laptop but imposes a floor on how often it can fire — <b>which is why a loop specified at every 10 minutes cannot run as a Claude cloud routine at all</b>, and has to live locally or in CI. On credentials the ranking is: CI is strongest because it can federate and hold no secret at all; local is next because a human is nearby; the vendor remotes are weakest, since they have neither a human nor federation and fall back to a long-lived token. Scope those narrowly, rotate them often, and put a gateway in front of them — a short-lived credential is worth more than a well-hidden one.",
|
|
5199
|
+
{ demoOnly: true }
|
|
5122
5200
|
),
|
|
5123
5201
|
],
|
|
5124
5202
|
};
|
|
@@ -6918,7 +6996,8 @@
|
|
|
6918
6996
|
],
|
|
6919
6997
|
},
|
|
6920
6998
|
fLegend(
|
|
6921
|
-
"<b>This is a readiness assessment, not a grade.</b> Like Vanta, it earns its value by measuring something you actually feel — the autonomy rate the first question asks for. An unanswered row is a named gap you can go fix on its detail page; a red one somewhere downstream is a control that is not yet real. And note the shape of the back half: ship, verify, operate, govern are exactly the places a person used to be the control. A no-human SDLC is not one with the humans deleted — it is one where every human-shaped hole has a named, exercised replacement."
|
|
6999
|
+
"<b>This is a readiness assessment, not a grade.</b> Like Vanta, it earns its value by measuring something you actually feel — the autonomy rate the first question asks for. An unanswered row is a named gap you can go fix on its detail page; a red one somewhere downstream is a control that is not yet real. And note the shape of the back half: ship, verify, operate, govern are exactly the places a person used to be the control. A no-human SDLC is not one with the humans deleted — it is one where every human-shaped hole has a named, exercised replacement.",
|
|
7000
|
+
{ demoOnly: true }
|
|
6922
7001
|
),
|
|
6923
7002
|
],
|
|
6924
7003
|
};
|
|
@@ -7022,6 +7101,7 @@
|
|
|
7022
7101
|
},
|
|
7023
7102
|
{
|
|
7024
7103
|
type: "callout",
|
|
7104
|
+
staticCopy: "installed Lisa setup guidance",
|
|
7025
7105
|
text: "The Health section’s in-band scan re-verifies most of these continuously once setup is complete; lisa doctor diagnoses any step that went sideways.",
|
|
7026
7106
|
},
|
|
7027
7107
|
],
|
|
@@ -7243,6 +7323,7 @@
|
|
|
7243
7323
|
},
|
|
7244
7324
|
{
|
|
7245
7325
|
type: "callout",
|
|
7326
|
+
staticCopy: "installed Lisa command taxonomy",
|
|
7246
7327
|
text: "Setup commands (/lisa:setup:*) live in the Setup checklist; quality one-offs (/lisa:improve:*, /lisa:security:*, mutation testing) are stack tools rather than core workflow.",
|
|
7247
7328
|
},
|
|
7248
7329
|
],
|
|
@@ -7255,6 +7336,7 @@
|
|
|
7255
7336
|
blocks: [
|
|
7256
7337
|
{
|
|
7257
7338
|
type: "callout",
|
|
7339
|
+
staticCopy: "shipped Lisa deterministic health contract",
|
|
7258
7340
|
text: '<b>Live health.</b> The console runs the shared deterministic Health v1 consumer against this local project. It reads and writes the same canonical result as <span class="mono">lisa health</span>; optional agentic evaluation remains a coding-harness concern.',
|
|
7259
7341
|
},
|
|
7260
7342
|
{
|
|
@@ -7332,6 +7414,7 @@
|
|
|
7332
7414
|
},
|
|
7333
7415
|
{
|
|
7334
7416
|
type: "callout",
|
|
7417
|
+
staticCopy: "shipped Lisa deterministic health contract",
|
|
7335
7418
|
text: '<b>How it works.</b> Browser runs are deliberately deterministic: the local server invokes the shared Health v1 consumer directly, without a child process or nested coding agent. The optional agentic layer is composed by <span class="mono">/lisa:health</span> in a supported coding harness.',
|
|
7336
7419
|
},
|
|
7337
7420
|
],
|
|
@@ -7399,6 +7482,7 @@
|
|
|
7399
7482
|
},
|
|
7400
7483
|
{
|
|
7401
7484
|
type: "callout",
|
|
7485
|
+
staticCopy: "fixed Lisa configuration file semantics",
|
|
7402
7486
|
text: "<b>Two config files.</b> .lisa.config.json is committed and shared; .lisa.config.local.json is gitignored and overrides it per-key. Keys flagged <i>local-only</i> below belong in the local file — lisa doctor warns when they are committed.",
|
|
7403
7487
|
},
|
|
7404
7488
|
],
|
|
@@ -7609,6 +7693,7 @@
|
|
|
7609
7693
|
blocks: [
|
|
7610
7694
|
{
|
|
7611
7695
|
type: "callout",
|
|
7696
|
+
staticCopy: "published starter-sync scope",
|
|
7612
7697
|
variant: "warn",
|
|
7613
7698
|
text: "<b>Planned feature.</b> This page documents the starter-sync contract and its settings; the sync engine and upstream-proposal flow are not wired up yet. Controls here preview the intended behavior.",
|
|
7614
7699
|
},
|
|
@@ -7712,6 +7797,7 @@
|
|
|
7712
7797
|
},
|
|
7713
7798
|
{
|
|
7714
7799
|
key: "",
|
|
7800
|
+
staticCopy: "installed Lisa command reference",
|
|
7715
7801
|
label: "Check for upstream changes",
|
|
7716
7802
|
desc: "Diffs the starter since the last synced commit and applies the changes to this project, respecting Lisa template semantics (create-only files the project now owns are never clobbered).",
|
|
7717
7803
|
control: {
|
|
@@ -7740,6 +7826,7 @@
|
|
|
7740
7826
|
},
|
|
7741
7827
|
{
|
|
7742
7828
|
key: "",
|
|
7829
|
+
staticCopy: "installed Lisa command reference",
|
|
7743
7830
|
label: "Scan for upstreamable changes",
|
|
7744
7831
|
desc: "One-off scan without a full sync.",
|
|
7745
7832
|
control: {
|
|
@@ -7755,12 +7842,14 @@
|
|
|
7755
7842
|
rows: [
|
|
7756
7843
|
{
|
|
7757
7844
|
key: "",
|
|
7845
|
+
staticCopy: "fixed starter ownership guidance",
|
|
7758
7846
|
label: "Upstreamable",
|
|
7759
7847
|
desc: "New shared utilities or hooks, dependency/security bumps, CI workflow fixes, lint-rule or config improvements with no project-specific identifiers.",
|
|
7760
7848
|
control: stat("proposed as starter issue", ""),
|
|
7761
7849
|
},
|
|
7762
7850
|
{
|
|
7763
7851
|
key: "",
|
|
7852
|
+
staticCopy: "fixed starter ownership guidance",
|
|
7764
7853
|
label: "Not upstreamable",
|
|
7765
7854
|
desc: "Anything referencing project names, product features, secrets/IDs, tracker keys, or business logic.",
|
|
7766
7855
|
control: stat("stays in the project", ""),
|
|
@@ -7810,6 +7899,7 @@
|
|
|
7810
7899
|
},
|
|
7811
7900
|
{
|
|
7812
7901
|
type: "callout",
|
|
7902
|
+
staticCopy: "fixed Lisa harness parity policy",
|
|
7813
7903
|
text: "<b>Parity guarantee.</b> Claude is the reference harness. Every other agent receives an equivalent generated surface; where a capability cannot be represented, the gap is documented rather than silently dropped.",
|
|
7814
7904
|
},
|
|
7815
7905
|
],
|
|
@@ -7837,6 +7927,7 @@
|
|
|
7837
7927
|
},
|
|
7838
7928
|
{
|
|
7839
7929
|
type: "callout",
|
|
7930
|
+
staticCopy: "fixed Lisa remote-environment discovery contract",
|
|
7840
7931
|
text: "<b>Project-aware by design.</b> Lisa combines the active tracker/source, detected project types, integration signals, and an optional <code>.lisa/remote-environment.json</code> project contract. A credential is never listed merely because Lisa knows how to support it.",
|
|
7841
7932
|
},
|
|
7842
7933
|
],
|
|
@@ -8092,6 +8183,7 @@
|
|
|
8092
8183
|
},
|
|
8093
8184
|
{
|
|
8094
8185
|
type: "callout",
|
|
8186
|
+
staticCopy: "fixed Linear mapping semantics",
|
|
8095
8187
|
text: 'Fixed native mappings: priority 0=None…4=Low, native estimates, components via a <span class="mono">component:</span> label prefix. Epics map to Projects, Stories to Issues, Sub-tasks to Sub-issues.',
|
|
8096
8188
|
},
|
|
8097
8189
|
],
|
|
@@ -8210,6 +8302,7 @@
|
|
|
8210
8302
|
blocks: [
|
|
8211
8303
|
{
|
|
8212
8304
|
type: "callout",
|
|
8305
|
+
staticCopy: "fixed Confluence lifecycle semantics",
|
|
8213
8306
|
text: "<b>Lifecycle via parent pages.</b> A PRD’s state is which parent page it sits under; transitions re-parent the page. This works with scoped tokens that cannot write Confluence labels.",
|
|
8214
8307
|
},
|
|
8215
8308
|
{
|
|
@@ -8490,6 +8583,7 @@
|
|
|
8490
8583
|
},
|
|
8491
8584
|
{
|
|
8492
8585
|
type: "callout",
|
|
8586
|
+
staticCopy: "fixed Lisa deployment environment semantics",
|
|
8493
8587
|
text: "GitHub silently auto-creates an unprotected environment the first time a workflow references it — Lisa provisions these explicitly so an approval gate always gates on something. Deployment branch policy is always pinned to the environment’s branch.",
|
|
8494
8588
|
},
|
|
8495
8589
|
{
|
|
@@ -8701,6 +8795,7 @@
|
|
|
8701
8795
|
},
|
|
8702
8796
|
{
|
|
8703
8797
|
type: "callout",
|
|
8798
|
+
staticCopy: "shipped Lisa pipeline support matrix",
|
|
8704
8799
|
text: "Not every stack gets every pipeline. The quality and release callers are seeded for all stacks; the agent and upkeep pipelines come from the TypeScript stack and are inherited by everything built on it; <b>zap-baseline.yml</b> is Harper Fabric and <b>publish-to-npm.yml</b> is npm-package. Rails swaps the quality caller for <b>quality-rails.yml</b>.",
|
|
8705
8800
|
},
|
|
8706
8801
|
],
|
|
@@ -8805,6 +8900,7 @@
|
|
|
8805
8900
|
},
|
|
8806
8901
|
{
|
|
8807
8902
|
type: "callout",
|
|
8903
|
+
staticCopy: "shipped Lisa automation support matrix",
|
|
8808
8904
|
text: "exploratory-bugs is created only for stacks that ship exploratory QA (expo, rails, harper-fabric); monitor is created unconditionally \u2014 it reports observability gaps rather than failing when no provider is connected.",
|
|
8809
8905
|
},
|
|
8810
8906
|
],
|
|
@@ -8909,6 +9005,7 @@
|
|
|
8909
9005
|
},
|
|
8910
9006
|
{
|
|
8911
9007
|
type: "callout",
|
|
9008
|
+
staticCopy: "fixed Lisa monitor migration semantics",
|
|
8912
9009
|
text: "lisa sync migrates a legacy monitor block when provenance proves Lisa auto-populated the old default, while preserving human-chosen legacy values. The runtime keeps accepting the deprecated aliases, prefers the provider-neutral key when both are present, and lisa doctor warns until migration is complete.",
|
|
8913
9010
|
},
|
|
8914
9011
|
],
|
|
@@ -9282,6 +9379,7 @@
|
|
|
9282
9379
|
},
|
|
9283
9380
|
{
|
|
9284
9381
|
type: "callout",
|
|
9382
|
+
staticCopy: "fixed Lisa lint override guidance",
|
|
9285
9383
|
text: "<b>Project overrides live in eslint.config.local.ts</b> (seeded once, never overwritten, spread after the factory so it wins). Two sanctioned patterns: re-declare a rule with looser <i>options</i> globally, or grandfather a path with a files-scoped block. Hand-refactoring to chase a tightened rule is the anti-pattern.",
|
|
9286
9384
|
},
|
|
9287
9385
|
],
|
|
@@ -9543,6 +9641,7 @@
|
|
|
9543
9641
|
},
|
|
9544
9642
|
{
|
|
9545
9643
|
type: "callout",
|
|
9644
|
+
staticCopy: "fixed Lisa verification policy",
|
|
9546
9645
|
variant: "warn",
|
|
9547
9646
|
text: "<b>Never --no-verify.</b> When a gate fails, the root cause gets fixed or a specific documented ignore is added with human sign-off. Blanket bypasses are blocked by a harness hook as well.",
|
|
9548
9647
|
},
|
|
@@ -9865,6 +9964,7 @@
|
|
|
9865
9964
|
},
|
|
9866
9965
|
{
|
|
9867
9966
|
type: "callout",
|
|
9967
|
+
staticCopy: "shipped Lisa Rails quality pipeline",
|
|
9868
9968
|
text: "Rails calls quality-rails.yml instead: rubocop lint, brakeman security, rspec on Postgres and MySQL, reek code quality, mutation, secret scanning, ast-grep, license compliance and ZAP.",
|
|
9869
9969
|
},
|
|
9870
9970
|
],
|
|
@@ -10272,6 +10372,7 @@
|
|
|
10272
10372
|
},
|
|
10273
10373
|
{
|
|
10274
10374
|
type: "callout",
|
|
10375
|
+
staticCopy: "shipped Lisa plugin parity catalog",
|
|
10275
10376
|
text: "<b>Parity reimplementations.</b> For non-Claude agents, curated plugin behavior is reimplemented as version-pinned Lisa skills: code-review, code-simplifier, coderabbit, safety-net rules, sentry SDK setup, sentry Seer, skill-creator. A drift detector fails CI when an upstream plugin moves ahead of its pin.",
|
|
10276
10377
|
},
|
|
10277
10378
|
],
|
|
@@ -10366,6 +10467,609 @@
|
|
|
10366
10467
|
],
|
|
10367
10468
|
};
|
|
10368
10469
|
|
|
10470
|
+
/*
|
|
10471
|
+
* Explicit provenance for renderer shapes whose values are not ordinary
|
|
10472
|
+
* config-key rows. Keys are semantic catalog paths, never row counts or
|
|
10473
|
+
* array indexes, so inserting or reordering a block cannot silently move
|
|
10474
|
+
* a declaration onto a different value.
|
|
10475
|
+
*/
|
|
10476
|
+
const UI_LIVE_BLOCKS = new Map([
|
|
10477
|
+
["doctor/doctor-report", "api:gate-report"],
|
|
10478
|
+
["setup/Required", "api:setup-readiness"],
|
|
10479
|
+
["setup/Optional", "api:setup-readiness"],
|
|
10480
|
+
["health/healthResults", "api:health"],
|
|
10481
|
+
["stacks/stacks", "probe:detected-stacks"],
|
|
10482
|
+
["remote/Variables", "window:LISA_REMOTE_ENVIRONMENT"],
|
|
10483
|
+
["remote/Startup Script", "window:LISA_REMOTE_ENVIRONMENT"],
|
|
10484
|
+
["deploy/deploy-pipeline-stages", "probe:deploy-pipeline-stages"],
|
|
10485
|
+
["automations/automations-scheduled-jobs", "probe:automations"],
|
|
10486
|
+
["monitor/Connected providers", "probe:observability-providers"],
|
|
10487
|
+
["ci/Quality jobs", "probe:ci-quality-jobs"],
|
|
10488
|
+
["repository/Rulesets", "api:github-repo"],
|
|
10489
|
+
["repository/Secrets", "api:github-repo"],
|
|
10490
|
+
["plugins/plugins-live", "probe:enabled-plugins"],
|
|
10491
|
+
]);
|
|
10492
|
+
const UI_STATIC_BLOCKS = new Map([
|
|
10493
|
+
[
|
|
10494
|
+
"overview/The four factories — humans hand off at the gates, never inside",
|
|
10495
|
+
"fixed Lisa factory model",
|
|
10496
|
+
],
|
|
10497
|
+
[
|
|
10498
|
+
"overview/Build lifecycle (fixed — only the status names are configurable)",
|
|
10499
|
+
"fixed Lisa lifecycle semantics",
|
|
10500
|
+
],
|
|
10501
|
+
["overview/PRD lifecycle (fixed)", "fixed Lisa lifecycle semantics"],
|
|
10502
|
+
[
|
|
10503
|
+
"overview/How configuration resolves/Resolution order",
|
|
10504
|
+
"fixed Lisa configuration precedence",
|
|
10505
|
+
],
|
|
10506
|
+
[
|
|
10507
|
+
"overview/How configuration resolves/Secrets",
|
|
10508
|
+
"fixed Lisa secret-handling contract",
|
|
10509
|
+
],
|
|
10510
|
+
[
|
|
10511
|
+
"workflow/The delivery loop these commands drive",
|
|
10512
|
+
"fixed Lisa delivery model",
|
|
10513
|
+
],
|
|
10514
|
+
["workflow/Delivery loop", "installed Lisa command reference"],
|
|
10515
|
+
["workflow/Commit & ship", "installed Lisa command reference"],
|
|
10516
|
+
["workflow/Feeder loops", "installed Lisa command reference"],
|
|
10517
|
+
["workflow/Onboarding & upkeep", "installed Lisa command reference"],
|
|
10518
|
+
["starters/Sync cycle", "fixed starter synchronization model"],
|
|
10519
|
+
[
|
|
10520
|
+
"starters/Sync behavior (starter → project)/Check for upstream changes",
|
|
10521
|
+
"installed Lisa command reference",
|
|
10522
|
+
],
|
|
10523
|
+
[
|
|
10524
|
+
"starters/Upstreaming (project → starter)/Scan for upstreamable changes",
|
|
10525
|
+
"installed Lisa command reference",
|
|
10526
|
+
],
|
|
10527
|
+
[
|
|
10528
|
+
"starters/What counts as generic/Upstreamable",
|
|
10529
|
+
"fixed starter ownership guidance",
|
|
10530
|
+
],
|
|
10531
|
+
[
|
|
10532
|
+
"starters/What counts as generic/Not upstreamable",
|
|
10533
|
+
"fixed starter ownership guidance",
|
|
10534
|
+
],
|
|
10535
|
+
[
|
|
10536
|
+
"prd/PRD lifecycle (fixed semantics, configurable names)",
|
|
10537
|
+
"fixed Lisa lifecycle semantics",
|
|
10538
|
+
],
|
|
10539
|
+
[
|
|
10540
|
+
"linting/Custom rules — code organization",
|
|
10541
|
+
"shipped Lisa rule catalog",
|
|
10542
|
+
],
|
|
10543
|
+
[
|
|
10544
|
+
"linting/Custom rules — component structure",
|
|
10545
|
+
"shipped Lisa rule catalog",
|
|
10546
|
+
],
|
|
10547
|
+
["linting/Custom rules — UI standards", "shipped Lisa rule catalog"],
|
|
10548
|
+
["linting/Custom rules — Phaser scenes", "shipped Lisa rule catalog"],
|
|
10549
|
+
["linting/Notable base-config severities", "shipped Lisa rule catalog"],
|
|
10550
|
+
["linting/ast-grep structural rules", "shipped Lisa rule catalog"],
|
|
10551
|
+
["testing/Runner per stack", "shipped Lisa runner support matrix"],
|
|
10552
|
+
["hooks/hooks", "shipped Lisa hook catalog"],
|
|
10553
|
+
["plugins/MCP servers", "shipped Lisa plugin catalog"],
|
|
10554
|
+
]);
|
|
10555
|
+
const UI_DEMO_BLOCKS = new Set([
|
|
10556
|
+
"overview/tiles",
|
|
10557
|
+
"factory-research/staff",
|
|
10558
|
+
"factory-research/How to run it",
|
|
10559
|
+
"factory-research/What a run takes",
|
|
10560
|
+
"factory-research/Connections — is the factory wired up?",
|
|
10561
|
+
"factory-plan/staff",
|
|
10562
|
+
"factory-plan/How to run it",
|
|
10563
|
+
"factory-plan/What a run takes",
|
|
10564
|
+
"factory-plan/Connections — is the factory wired up?",
|
|
10565
|
+
"factory-plan/PRD lifecycle roles",
|
|
10566
|
+
"factory-implement/staff",
|
|
10567
|
+
"factory-implement/How to run it",
|
|
10568
|
+
"factory-implement/What a run takes",
|
|
10569
|
+
"factory-implement/Connections — is the factory wired up?",
|
|
10570
|
+
"factory-implement/Gates",
|
|
10571
|
+
"factory-implement/Work-unit lifecycle roles",
|
|
10572
|
+
"factory-verify/staff",
|
|
10573
|
+
"factory-verify/How to run it",
|
|
10574
|
+
"factory-verify/What a run takes",
|
|
10575
|
+
"factory-verify/Connections — is the factory wired up?",
|
|
10576
|
+
"signals/How it senses",
|
|
10577
|
+
"signals/Signal coverage",
|
|
10578
|
+
"credentials/Agent identities",
|
|
10579
|
+
"environments/staff",
|
|
10580
|
+
"environments/environments",
|
|
10581
|
+
"readiness/intake",
|
|
10582
|
+
"agents/Fleet targets",
|
|
10583
|
+
"pipelines/Pipeline inventory",
|
|
10584
|
+
]);
|
|
10585
|
+
const UI_LIVE_ROWS = new Map([
|
|
10586
|
+
["health/Lisa version/Installed", "probe:lisa-version"],
|
|
10587
|
+
["health/Lisa version/Latest published", "probe:lisa-version"],
|
|
10588
|
+
["health/Lisa version/Update", "probe:lisa-version"],
|
|
10589
|
+
["health/In-band scan/Latest deterministic check", "api:health"],
|
|
10590
|
+
]);
|
|
10591
|
+
const UI_STATIC_ROWS = new Map([
|
|
10592
|
+
[
|
|
10593
|
+
"health/In-band scan/Run deterministic health check",
|
|
10594
|
+
"installed Lisa health action",
|
|
10595
|
+
],
|
|
10596
|
+
["deploy/Pipeline/CI/CD provider", "fixed supported pipeline host"],
|
|
10597
|
+
["deploy/Pipeline/Workflows", "fixed Lisa workflow reference"],
|
|
10598
|
+
["pipelines/Host/Pipeline host", "fixed supported pipeline host"],
|
|
10599
|
+
["pipelines/Host/Delivery model", "fixed Lisa delivery model"],
|
|
10600
|
+
["repository/Host/Git host", "fixed supported repository host"],
|
|
10601
|
+
]);
|
|
10602
|
+
const UI_DEMO_ROWS = new Set([
|
|
10603
|
+
"automations/Fleet controls/Fleet health",
|
|
10604
|
+
"automations/Fleet controls/Run an intake cycle now",
|
|
10605
|
+
"automations/Fleet controls/Pause the fleet",
|
|
10606
|
+
]);
|
|
10607
|
+
const UI_PRESERVED_TABLE_ROWS = new Map([
|
|
10608
|
+
[
|
|
10609
|
+
"ci/Quality jobs",
|
|
10610
|
+
{
|
|
10611
|
+
identity: "jobId",
|
|
10612
|
+
ids: new Set([
|
|
10613
|
+
"lint",
|
|
10614
|
+
"lint_slow",
|
|
10615
|
+
"typecheck",
|
|
10616
|
+
"format",
|
|
10617
|
+
"build",
|
|
10618
|
+
"test:unit",
|
|
10619
|
+
"test:integration",
|
|
10620
|
+
"playwright_e2e",
|
|
10621
|
+
"maestro_e2e",
|
|
10622
|
+
"e2e_coverage",
|
|
10623
|
+
"test:mutation",
|
|
10624
|
+
"verification_coverage",
|
|
10625
|
+
"dead_code",
|
|
10626
|
+
"sg_scan",
|
|
10627
|
+
"npm_security_scan",
|
|
10628
|
+
"sonarcloud",
|
|
10629
|
+
"snyk",
|
|
10630
|
+
"secret_scanning",
|
|
10631
|
+
"license_compliance",
|
|
10632
|
+
"compliance_validation",
|
|
10633
|
+
"approval_gate",
|
|
10634
|
+
]),
|
|
10635
|
+
staticCopy: "shipped Lisa quality-job catalog",
|
|
10636
|
+
},
|
|
10637
|
+
],
|
|
10638
|
+
[
|
|
10639
|
+
"monitor/Connected providers",
|
|
10640
|
+
{
|
|
10641
|
+
identity: "provider",
|
|
10642
|
+
ids: new Set(["sentry", "cloudwatch-alarms", "x-ray"]),
|
|
10643
|
+
staticCopy: "shipped Lisa observability-provider catalog",
|
|
10644
|
+
},
|
|
10645
|
+
],
|
|
10646
|
+
]);
|
|
10647
|
+
|
|
10648
|
+
/**
|
|
10649
|
+
* Declare the fixed and probe-painted leaves of a partially live table.
|
|
10650
|
+
* The identifier allowlist is intentionally closed: a newly added row
|
|
10651
|
+
* receives no declaration and the audit bites until its source is named.
|
|
10652
|
+
*/
|
|
10653
|
+
function declarePreservedTableLeaves(block, path) {
|
|
10654
|
+
const registry = UI_PRESERVED_TABLE_ROWS.get(path);
|
|
10655
|
+
if (!registry) return;
|
|
10656
|
+
const seen = new Set();
|
|
10657
|
+
(block.rows || []).forEach(row => {
|
|
10658
|
+
if (!Array.isArray(row)) return;
|
|
10659
|
+
const liveCell = row.find(
|
|
10660
|
+
cell =>
|
|
10661
|
+
cell &&
|
|
10662
|
+
typeof cell[registry.identity] === "string" &&
|
|
10663
|
+
registry.ids.has(cell[registry.identity])
|
|
10664
|
+
);
|
|
10665
|
+
if (!liveCell || seen.has(liveCell[registry.identity])) return;
|
|
10666
|
+
seen.add(liveCell[registry.identity]);
|
|
10667
|
+
row.staticCopy = registry.staticCopy;
|
|
10668
|
+
row.forEach(cell => {
|
|
10669
|
+
if (!cell || typeof cell !== "object") return;
|
|
10670
|
+
if (cell === liveCell) cell.liveSource = block.liveSource;
|
|
10671
|
+
else cell.staticCopy = registry.staticCopy;
|
|
10672
|
+
});
|
|
10673
|
+
});
|
|
10674
|
+
}
|
|
10675
|
+
|
|
10676
|
+
/** Apply the explicit semantic-path registry to its catalog shapes. */
|
|
10677
|
+
function declareUiCatalogProvenance(catalog) {
|
|
10678
|
+
const declareBlocks = (sectionId, blocks, ancestry) => {
|
|
10679
|
+
(blocks || []).forEach((block, blockIndex) => {
|
|
10680
|
+
const blockName =
|
|
10681
|
+
block.id ||
|
|
10682
|
+
block.card ||
|
|
10683
|
+
block.title ||
|
|
10684
|
+
block.type ||
|
|
10685
|
+
`block ${blockIndex + 1}`;
|
|
10686
|
+
const pathParts = [sectionId, ...ancestry, blockName];
|
|
10687
|
+
const path = pathParts.join("/");
|
|
10688
|
+
if (block.type === "tabs") {
|
|
10689
|
+
(block.tabs || []).forEach((tab, tabIndex) =>
|
|
10690
|
+
declareBlocks(sectionId, tab.blocks, [
|
|
10691
|
+
...ancestry,
|
|
10692
|
+
tab.label || `tab ${tabIndex + 1}`,
|
|
10693
|
+
])
|
|
10694
|
+
);
|
|
10695
|
+
return;
|
|
10696
|
+
}
|
|
10697
|
+
if (UI_LIVE_BLOCKS.has(path)) {
|
|
10698
|
+
block.liveSource = UI_LIVE_BLOCKS.get(path);
|
|
10699
|
+
if (
|
|
10700
|
+
path === "ci/Quality jobs" ||
|
|
10701
|
+
path === "monitor/Connected providers"
|
|
10702
|
+
) {
|
|
10703
|
+
block.preserveStructure = true;
|
|
10704
|
+
declarePreservedTableLeaves(block, path);
|
|
10705
|
+
}
|
|
10706
|
+
} else if (UI_STATIC_BLOCKS.has(path)) {
|
|
10707
|
+
block.staticCopy = UI_STATIC_BLOCKS.get(path);
|
|
10708
|
+
} else if (UI_DEMO_BLOCKS.has(path)) {
|
|
10709
|
+
block.demoOnly = true;
|
|
10710
|
+
}
|
|
10711
|
+
if (block.type) return;
|
|
10712
|
+
(block.rows || []).forEach((row, rowIndex) => {
|
|
10713
|
+
const rowPath = [
|
|
10714
|
+
...pathParts,
|
|
10715
|
+
row.label || row.key || `row ${rowIndex + 1}`,
|
|
10716
|
+
].join("/");
|
|
10717
|
+
if (UI_LIVE_ROWS.has(rowPath)) {
|
|
10718
|
+
row.liveSource = UI_LIVE_ROWS.get(rowPath);
|
|
10719
|
+
} else if (UI_STATIC_ROWS.has(rowPath)) {
|
|
10720
|
+
row.staticCopy = UI_STATIC_ROWS.get(rowPath);
|
|
10721
|
+
} else if (UI_DEMO_ROWS.has(rowPath)) {
|
|
10722
|
+
row.demoOnly = true;
|
|
10723
|
+
}
|
|
10724
|
+
});
|
|
10725
|
+
});
|
|
10726
|
+
};
|
|
10727
|
+
Object.entries(catalog.sections || {}).forEach(([sectionId, section]) =>
|
|
10728
|
+
declareBlocks(sectionId, section.blocks, [])
|
|
10729
|
+
);
|
|
10730
|
+
}
|
|
10731
|
+
declareUiCatalogProvenance(DATA);
|
|
10732
|
+
|
|
10733
|
+
/**
|
|
10734
|
+
* Return the one declared provenance for a renderer-bearing catalog shape.
|
|
10735
|
+
* The declaration lives on the shape that owns the value: a config row's
|
|
10736
|
+
* `key`, an asynchronous `liveSource`, an intentional `demoOnly` value, or
|
|
10737
|
+
* a narrowly explained `staticCopy` value. More than one declaration is as
|
|
10738
|
+
* ambiguous as none and is rejected by the same audit.
|
|
10739
|
+
*/
|
|
10740
|
+
function catalogValueProvenance(value) {
|
|
10741
|
+
if (!value || typeof value !== "object") return null;
|
|
10742
|
+
const declared = [];
|
|
10743
|
+
if (typeof value.key === "string" && value.key.trim().length > 0) {
|
|
10744
|
+
declared.push({ kind: "key", source: value.key });
|
|
10745
|
+
}
|
|
10746
|
+
if (
|
|
10747
|
+
typeof value.liveSource === "string" &&
|
|
10748
|
+
value.liveSource.trim().length > 0
|
|
10749
|
+
) {
|
|
10750
|
+
declared.push({ kind: "liveSource", source: value.liveSource });
|
|
10751
|
+
}
|
|
10752
|
+
if (value.demoOnly === true) {
|
|
10753
|
+
declared.push({ kind: "demoOnly", source: "direct-file demo" });
|
|
10754
|
+
}
|
|
10755
|
+
if (
|
|
10756
|
+
typeof value.staticCopy === "string" &&
|
|
10757
|
+
value.staticCopy.trim().length > 0
|
|
10758
|
+
) {
|
|
10759
|
+
declared.push({ kind: "staticCopy", source: value.staticCopy });
|
|
10760
|
+
}
|
|
10761
|
+
return declared.length === 1
|
|
10762
|
+
? declared[0]
|
|
10763
|
+
: { kind: "unclassified", source: "", declared: declared.length };
|
|
10764
|
+
}
|
|
10765
|
+
|
|
10766
|
+
/** Attach audit metadata without changing the JSON-like catalog surface. */
|
|
10767
|
+
function rememberCatalogProvenance(value, provenance, path) {
|
|
10768
|
+
if (!value || typeof value !== "object") return;
|
|
10769
|
+
Object.defineProperty(value, "__lisaValueProvenance", {
|
|
10770
|
+
configurable: true,
|
|
10771
|
+
enumerable: false,
|
|
10772
|
+
value: { ...provenance, path },
|
|
10773
|
+
writable: true,
|
|
10774
|
+
});
|
|
10775
|
+
}
|
|
10776
|
+
|
|
10777
|
+
/** Whether a block renderer consumes project-shaped values from the block. */
|
|
10778
|
+
function isValueBearingBlock(block) {
|
|
10779
|
+
return [
|
|
10780
|
+
"table",
|
|
10781
|
+
"tiles",
|
|
10782
|
+
"flow",
|
|
10783
|
+
"stacks",
|
|
10784
|
+
"hooks",
|
|
10785
|
+
"callout",
|
|
10786
|
+
"checklist",
|
|
10787
|
+
"plugins-live",
|
|
10788
|
+
"staff",
|
|
10789
|
+
"environments",
|
|
10790
|
+
"intake",
|
|
10791
|
+
].includes(block.type);
|
|
10792
|
+
}
|
|
10793
|
+
|
|
10794
|
+
/** Every typed block the renderer knows how to build. */
|
|
10795
|
+
const UI_BLOCK_RENDERERS = new Set([
|
|
10796
|
+
"doctor-report",
|
|
10797
|
+
"tiles",
|
|
10798
|
+
"flow",
|
|
10799
|
+
"stacks",
|
|
10800
|
+
"hooks",
|
|
10801
|
+
"callout",
|
|
10802
|
+
"table",
|
|
10803
|
+
"tabs",
|
|
10804
|
+
"checklist",
|
|
10805
|
+
"plugins-live",
|
|
10806
|
+
"staff",
|
|
10807
|
+
"environments",
|
|
10808
|
+
"intake",
|
|
10809
|
+
]);
|
|
10810
|
+
|
|
10811
|
+
/** Stable table-row name used in audit diagnostics and DOM evidence. */
|
|
10812
|
+
function tableRowName(row, rowIndex) {
|
|
10813
|
+
if (!Array.isArray(row)) return `row ${rowIndex + 1}`;
|
|
10814
|
+
const firstValue = row.find(
|
|
10815
|
+
cell => cell && typeof cell.v === "string" && cell.v.length > 0
|
|
10816
|
+
);
|
|
10817
|
+
return firstValue ? firstValue.v : `row ${rowIndex + 1}`;
|
|
10818
|
+
}
|
|
10819
|
+
|
|
10820
|
+
/**
|
|
10821
|
+
* Inspect the same shapes the renderer can turn into visible values.
|
|
10822
|
+
* This deliberately discovers sections, blocks, and rows from the catalog;
|
|
10823
|
+
* no row or section count can go stale when the console grows.
|
|
10824
|
+
*/
|
|
10825
|
+
function auditUiCatalog(catalog) {
|
|
10826
|
+
const violations = [];
|
|
10827
|
+
let inspected = 0;
|
|
10828
|
+
const inspect = (value, path, inherited = null) => {
|
|
10829
|
+
inspected += 1;
|
|
10830
|
+
const direct = catalogValueProvenance(value) || {
|
|
10831
|
+
kind: "unclassified",
|
|
10832
|
+
source: "",
|
|
10833
|
+
declared: 0,
|
|
10834
|
+
};
|
|
10835
|
+
const provenance =
|
|
10836
|
+
direct.kind === "unclassified" &&
|
|
10837
|
+
direct.declared === 0 &&
|
|
10838
|
+
inherited &&
|
|
10839
|
+
inherited.kind !== "unclassified"
|
|
10840
|
+
? { kind: inherited.kind, source: inherited.source }
|
|
10841
|
+
: direct;
|
|
10842
|
+
rememberCatalogProvenance(value, provenance, path);
|
|
10843
|
+
if (provenance.kind === "unclassified") {
|
|
10844
|
+
const suffix =
|
|
10845
|
+
provenance.declared > 1
|
|
10846
|
+
? ` (${provenance.declared} provenance declarations)`
|
|
10847
|
+
: "";
|
|
10848
|
+
violations.push(path.join(" > ") + suffix);
|
|
10849
|
+
}
|
|
10850
|
+
return provenance;
|
|
10851
|
+
};
|
|
10852
|
+
const inspectBlocks = (sectionId, blocks, ancestry) => {
|
|
10853
|
+
(blocks || []).forEach((block, blockIndex) => {
|
|
10854
|
+
const blockName =
|
|
10855
|
+
block.id ||
|
|
10856
|
+
block.card ||
|
|
10857
|
+
block.title ||
|
|
10858
|
+
block.type ||
|
|
10859
|
+
`block ${blockIndex + 1}`;
|
|
10860
|
+
const path = [sectionId, ...ancestry, blockName];
|
|
10861
|
+
if (block.type === "tabs") {
|
|
10862
|
+
(block.tabs || []).forEach((tab, tabIndex) =>
|
|
10863
|
+
inspectBlocks(sectionId, tab.blocks, [
|
|
10864
|
+
...ancestry,
|
|
10865
|
+
tab.label || `tab ${tabIndex + 1}`,
|
|
10866
|
+
])
|
|
10867
|
+
);
|
|
10868
|
+
return;
|
|
10869
|
+
}
|
|
10870
|
+
if (block.type && !UI_BLOCK_RENDERERS.has(block.type)) {
|
|
10871
|
+
inspected += 1;
|
|
10872
|
+
violations.push(
|
|
10873
|
+
path.join(" > ") + ` (unsupported renderer type ${block.type})`
|
|
10874
|
+
);
|
|
10875
|
+
return;
|
|
10876
|
+
}
|
|
10877
|
+
const directBlockProvenance = catalogValueProvenance(block);
|
|
10878
|
+
const blockProvenance =
|
|
10879
|
+
isValueBearingBlock(block) ||
|
|
10880
|
+
directBlockProvenance.kind !== "unclassified"
|
|
10881
|
+
? inspect(block, path)
|
|
10882
|
+
: null;
|
|
10883
|
+
if (block.type === "table") {
|
|
10884
|
+
(block.rows || []).forEach((row, rowIndex) => {
|
|
10885
|
+
const rowPath = [...path, tableRowName(row, rowIndex)];
|
|
10886
|
+
const inherited =
|
|
10887
|
+
block.preserveStructure === true ? null : blockProvenance;
|
|
10888
|
+
const rowProvenance = inspect(row, rowPath, inherited);
|
|
10889
|
+
(row || []).forEach((cell, cellIndex) =>
|
|
10890
|
+
inspect(
|
|
10891
|
+
cell,
|
|
10892
|
+
[...rowPath, `cell ${cellIndex + 1}`],
|
|
10893
|
+
block.preserveStructure === true ? null : rowProvenance
|
|
10894
|
+
)
|
|
10895
|
+
);
|
|
10896
|
+
});
|
|
10897
|
+
return;
|
|
10898
|
+
}
|
|
10899
|
+
if (block.type) return;
|
|
10900
|
+
(block.rows || []).forEach((row, rowIndex) => {
|
|
10901
|
+
if (!row || !row.control) return;
|
|
10902
|
+
inspect(
|
|
10903
|
+
row,
|
|
10904
|
+
[...path, row.label || row.key || `row ${rowIndex + 1}`],
|
|
10905
|
+
blockProvenance
|
|
10906
|
+
);
|
|
10907
|
+
});
|
|
10908
|
+
});
|
|
10909
|
+
};
|
|
10910
|
+
Object.entries(catalog.sections || {}).forEach(([sectionId, section]) =>
|
|
10911
|
+
inspectBlocks(sectionId, section.blocks, [])
|
|
10912
|
+
);
|
|
10913
|
+
if (inspected === 0) {
|
|
10914
|
+
violations.push("catalog audit inspected zero renderer values");
|
|
10915
|
+
}
|
|
10916
|
+
const result = {
|
|
10917
|
+
inspected,
|
|
10918
|
+
sections: Object.keys(catalog.sections || {}).length,
|
|
10919
|
+
violations,
|
|
10920
|
+
};
|
|
10921
|
+
if (violations.length > 0) {
|
|
10922
|
+
throw new Error(
|
|
10923
|
+
"UI demo-data audit found unclassified rendered values:\n- " +
|
|
10924
|
+
violations.join("\n- ")
|
|
10925
|
+
);
|
|
10926
|
+
}
|
|
10927
|
+
return result;
|
|
10928
|
+
}
|
|
10929
|
+
|
|
10930
|
+
/** Read a dot path while preserving the difference between missing and falsey. */
|
|
10931
|
+
function liveConfigEntry(config, dotPath) {
|
|
10932
|
+
let node = config;
|
|
10933
|
+
for (const segment of dotPath.split(".")) {
|
|
10934
|
+
if (
|
|
10935
|
+
!node ||
|
|
10936
|
+
typeof node !== "object" ||
|
|
10937
|
+
Array.isArray(node) ||
|
|
10938
|
+
!Object.hasOwn(node, segment)
|
|
10939
|
+
) {
|
|
10940
|
+
return { present: false, value: undefined };
|
|
10941
|
+
}
|
|
10942
|
+
node = node[segment];
|
|
10943
|
+
}
|
|
10944
|
+
return { present: true, value: node };
|
|
10945
|
+
}
|
|
10946
|
+
|
|
10947
|
+
/** Replace a missing config value with a renderer-native unknown state. */
|
|
10948
|
+
function unknownControl(row, reason) {
|
|
10949
|
+
row.control = {
|
|
10950
|
+
...(row.control || {}),
|
|
10951
|
+
type: "static",
|
|
10952
|
+
value: "unknown",
|
|
10953
|
+
cls: "warn",
|
|
10954
|
+
reason,
|
|
10955
|
+
};
|
|
10956
|
+
}
|
|
10957
|
+
|
|
10958
|
+
/** Initialize a probe/API table without leaving any demo row visible. */
|
|
10959
|
+
function resetLiveBlock(block) {
|
|
10960
|
+
if (block.preserveStructure === true) {
|
|
10961
|
+
(block.rows || []).forEach(row => {
|
|
10962
|
+
if (!Array.isArray(row)) return;
|
|
10963
|
+
row.forEach(cell => {
|
|
10964
|
+
if (cell && cell.t === "status") {
|
|
10965
|
+
cell.v = null;
|
|
10966
|
+
cell.reason = "pending live source: " + block.liveSource;
|
|
10967
|
+
}
|
|
10968
|
+
});
|
|
10969
|
+
});
|
|
10970
|
+
return;
|
|
10971
|
+
}
|
|
10972
|
+
if (block.type === "table") {
|
|
10973
|
+
block.rows = [];
|
|
10974
|
+
block.empty = {
|
|
10975
|
+
state: "unknown",
|
|
10976
|
+
message: "Waiting for " + block.liveSource,
|
|
10977
|
+
};
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10980
|
+
|
|
10981
|
+
/**
|
|
10982
|
+
* Scrub demo state before the first live render and hydrate config-keyed
|
|
10983
|
+
* controls. The function mutates the in-memory catalog once so later full
|
|
10984
|
+
* rerenders cannot resurrect a demo fallback.
|
|
10985
|
+
*/
|
|
10986
|
+
function prepareUiCatalogForLive(catalog, config) {
|
|
10987
|
+
const usableConfig =
|
|
10988
|
+
config && typeof config === "object" && !Array.isArray(config)
|
|
10989
|
+
? config
|
|
10990
|
+
: {};
|
|
10991
|
+
let removedDemo = 0;
|
|
10992
|
+
let pending = 0;
|
|
10993
|
+
const prepareBlocks = blocks =>
|
|
10994
|
+
(blocks || []).flatMap(block => {
|
|
10995
|
+
if (block.type === "tabs") {
|
|
10996
|
+
(block.tabs || []).forEach(tab => {
|
|
10997
|
+
tab.blocks = prepareBlocks(tab.blocks);
|
|
10998
|
+
});
|
|
10999
|
+
return [block];
|
|
11000
|
+
}
|
|
11001
|
+
const blockProvenance = block.__lisaValueProvenance;
|
|
11002
|
+
if (blockProvenance?.kind === "demoOnly") {
|
|
11003
|
+
removedDemo += 1;
|
|
11004
|
+
return [];
|
|
11005
|
+
}
|
|
11006
|
+
if (blockProvenance?.kind === "liveSource") {
|
|
11007
|
+
pending += 1;
|
|
11008
|
+
resetLiveBlock(block);
|
|
11009
|
+
}
|
|
11010
|
+
if (!block.type && Array.isArray(block.rows)) {
|
|
11011
|
+
block.rows = block.rows.flatMap(row => {
|
|
11012
|
+
const provenance = row.__lisaValueProvenance;
|
|
11013
|
+
if (provenance?.kind === "demoOnly") {
|
|
11014
|
+
removedDemo += 1;
|
|
11015
|
+
return [];
|
|
11016
|
+
}
|
|
11017
|
+
if (provenance?.kind === "key") {
|
|
11018
|
+
const entry = liveConfigEntry(
|
|
11019
|
+
usableConfig,
|
|
11020
|
+
provenance.source
|
|
11021
|
+
);
|
|
11022
|
+
if (entry.present) {
|
|
11023
|
+
const liveControl = hydrateControl(
|
|
11024
|
+
row.control,
|
|
11025
|
+
entry.value
|
|
11026
|
+
);
|
|
11027
|
+
if (liveControl) row.control = liveControl;
|
|
11028
|
+
else unknownControl(row, "invalid live config value");
|
|
11029
|
+
} else unknownControl(row, "missing live config key");
|
|
11030
|
+
} else if (provenance?.kind === "liveSource") {
|
|
11031
|
+
pending += 1;
|
|
11032
|
+
unknownControl(row, "pending " + provenance.source);
|
|
11033
|
+
}
|
|
11034
|
+
return [row];
|
|
11035
|
+
});
|
|
11036
|
+
}
|
|
11037
|
+
return [block];
|
|
11038
|
+
});
|
|
11039
|
+
Object.values(catalog.sections || {}).forEach(section => {
|
|
11040
|
+
section.blocks = prepareBlocks(section.blocks);
|
|
11041
|
+
});
|
|
11042
|
+
const remainingDemo = countCatalogProvenance(catalog, "demoOnly");
|
|
11043
|
+
return {
|
|
11044
|
+
liveMode: true,
|
|
11045
|
+
removedDemo,
|
|
11046
|
+
pending,
|
|
11047
|
+
remainingDemo,
|
|
11048
|
+
};
|
|
11049
|
+
}
|
|
11050
|
+
|
|
11051
|
+
/** Count audited shapes of one provenance kind after preparation. */
|
|
11052
|
+
function countCatalogProvenance(catalog, kind) {
|
|
11053
|
+
let count = 0;
|
|
11054
|
+
const visitBlocks = blocks =>
|
|
11055
|
+
(blocks || []).forEach(block => {
|
|
11056
|
+
if (block.__lisaValueProvenance?.kind === kind) count += 1;
|
|
11057
|
+
if (block.type === "tabs") {
|
|
11058
|
+
(block.tabs || []).forEach(tab => visitBlocks(tab.blocks));
|
|
11059
|
+
}
|
|
11060
|
+
if (!block.type) {
|
|
11061
|
+
(block.rows || []).forEach(row => {
|
|
11062
|
+
if (row.__lisaValueProvenance?.kind === kind) count += 1;
|
|
11063
|
+
});
|
|
11064
|
+
}
|
|
11065
|
+
});
|
|
11066
|
+
Object.values(catalog.sections || {}).forEach(section =>
|
|
11067
|
+
visitBlocks(section.blocks)
|
|
11068
|
+
);
|
|
11069
|
+
return count;
|
|
11070
|
+
}
|
|
11071
|
+
/* LISA_UI_CATALOG_END */
|
|
11072
|
+
|
|
10369
11073
|
/* ------------------------------------------------------------------ *
|
|
10370
11074
|
* Rendering + interactions (prototype: state is in-memory only)
|
|
10371
11075
|
* ------------------------------------------------------------------ */
|
|
@@ -10375,6 +11079,9 @@
|
|
|
10375
11079
|
const $savebar = document.getElementById("savebar");
|
|
10376
11080
|
const $toast = document.getElementById("toast");
|
|
10377
11081
|
const dirty = new Set();
|
|
11082
|
+
let lisaLiveMode = false;
|
|
11083
|
+
let lisaCatalogAudit = null;
|
|
11084
|
+
let lisaCatalogPreparation = null;
|
|
10378
11085
|
|
|
10379
11086
|
/* Live enabled-plugins state, hydrated from GET /api/status. Starts as
|
|
10380
11087
|
"loading" so the Plugins section never presents demo toggles as truth. */
|
|
@@ -10497,6 +11204,27 @@
|
|
|
10497
11204
|
updateSavebar();
|
|
10498
11205
|
}
|
|
10499
11206
|
|
|
11207
|
+
/** Mark the DOM owner of a catalog value with its audited provenance. */
|
|
11208
|
+
function markRenderedValue(node, value) {
|
|
11209
|
+
if (!node || !value) return node;
|
|
11210
|
+
const provenance =
|
|
11211
|
+
value.__lisaValueProvenance || catalogValueProvenance(value);
|
|
11212
|
+
if (!provenance || provenance.kind === "unclassified") return node;
|
|
11213
|
+
node.dataset.lisaValueSource = provenance.kind;
|
|
11214
|
+
node.dataset.lisaValuePath = provenance.path
|
|
11215
|
+
? provenance.path.join(" > ")
|
|
11216
|
+
: provenance.source;
|
|
11217
|
+
return node;
|
|
11218
|
+
}
|
|
11219
|
+
|
|
11220
|
+
/** Re-audit after catalog mutation and expose the latest browser evidence. */
|
|
11221
|
+
function refreshCatalogAudit() {
|
|
11222
|
+
lisaCatalogAudit = auditUiCatalog(DATA);
|
|
11223
|
+
if (window.LISA_UI_CATALOG_STATE) {
|
|
11224
|
+
window.LISA_UI_CATALOG_STATE.audit = lisaCatalogAudit;
|
|
11225
|
+
}
|
|
11226
|
+
}
|
|
11227
|
+
|
|
10500
11228
|
let ctlSeq = 0;
|
|
10501
11229
|
/* Propagate a shared setting to every control bound to the same syncKey:
|
|
10502
11230
|
first into DATA so a re-render keeps the new value, then into any
|
|
@@ -10575,11 +11303,22 @@
|
|
|
10575
11303
|
} else if (control.type === "select") {
|
|
10576
11304
|
const s = document.createElement("select");
|
|
10577
11305
|
s.className = "ctl" + (control.mono ? " mono" : "");
|
|
10578
|
-
|
|
11306
|
+
const options = control.options || [];
|
|
11307
|
+
const hasMatchingValue =
|
|
11308
|
+
control.value !== "" && options.some(o => o === control.value);
|
|
11309
|
+
if (!hasMatchingValue) {
|
|
11310
|
+
const unknown = document.createElement("option");
|
|
11311
|
+
unknown.value = "";
|
|
11312
|
+
unknown.textContent = "unknown";
|
|
11313
|
+
unknown.selected = true;
|
|
11314
|
+
s.dataset.state = "unknown";
|
|
11315
|
+
s.appendChild(unknown);
|
|
11316
|
+
}
|
|
11317
|
+
options.forEach(o => {
|
|
10579
11318
|
const opt = document.createElement("option");
|
|
10580
11319
|
opt.value = o;
|
|
10581
11320
|
opt.textContent = o;
|
|
10582
|
-
if (o === control.value) opt.selected = true;
|
|
11321
|
+
if (hasMatchingValue && o === control.value) opt.selected = true;
|
|
10583
11322
|
s.appendChild(opt);
|
|
10584
11323
|
});
|
|
10585
11324
|
bind(s, i => i.closest(".row") || i.closest("tr"));
|
|
@@ -10786,7 +11525,7 @@
|
|
|
10786
11525
|
? { ...row.control, syncKey: row.key }
|
|
10787
11526
|
: row.control;
|
|
10788
11527
|
r.appendChild(buildControl(control, row.key));
|
|
10789
|
-
return r;
|
|
11528
|
+
return markRenderedValue(r, row);
|
|
10790
11529
|
}
|
|
10791
11530
|
|
|
10792
11531
|
function buildCard(block) {
|
|
@@ -10906,12 +11645,13 @@
|
|
|
10906
11645
|
const tbody = el("tbody");
|
|
10907
11646
|
block.rows.forEach(cells => {
|
|
10908
11647
|
const tr = el("tr");
|
|
11648
|
+
markRenderedValue(tr, cells);
|
|
10909
11649
|
if (cells && cells._automationId) {
|
|
10910
11650
|
tr.dataset.automationId = cells._automationId;
|
|
10911
11651
|
}
|
|
10912
11652
|
cells.forEach(cell => {
|
|
10913
11653
|
if (cell.t === "status") {
|
|
10914
|
-
tr.appendChild(buildStatusCell(cell));
|
|
11654
|
+
tr.appendChild(markRenderedValue(buildStatusCell(cell), cell));
|
|
10915
11655
|
return;
|
|
10916
11656
|
}
|
|
10917
11657
|
const td = el("td");
|
|
@@ -11039,7 +11779,7 @@
|
|
|
11039
11779
|
buildControl({ ...cell, type: cell.t, value: cell.v }, null)
|
|
11040
11780
|
);
|
|
11041
11781
|
}
|
|
11042
|
-
tr.appendChild(td);
|
|
11782
|
+
tr.appendChild(markRenderedValue(td, cell));
|
|
11043
11783
|
});
|
|
11044
11784
|
tbody.appendChild(tr);
|
|
11045
11785
|
});
|
|
@@ -11219,6 +11959,7 @@
|
|
|
11219
11959
|
"div",
|
|
11220
11960
|
"callout" + (block.variant === "warn" ? " warn" : "")
|
|
11221
11961
|
);
|
|
11962
|
+
box.dataset.lisaRenderer = "callout";
|
|
11222
11963
|
// Single child span: the callout is flex, and multiple inline nodes
|
|
11223
11964
|
// would each become flex items and render as broken columns.
|
|
11224
11965
|
box.appendChild(el("span", "", block.text));
|
|
@@ -11499,20 +12240,24 @@
|
|
|
11499
12240
|
}
|
|
11500
12241
|
|
|
11501
12242
|
function buildBlock(block) {
|
|
11502
|
-
|
|
11503
|
-
if (block.type === "
|
|
11504
|
-
if (block.type === "
|
|
11505
|
-
if (block.type === "
|
|
11506
|
-
if (block.type === "
|
|
11507
|
-
if (block.type === "
|
|
11508
|
-
if (block.type === "
|
|
11509
|
-
if (block.type === "
|
|
11510
|
-
if (block.type === "
|
|
11511
|
-
if (block.type === "
|
|
11512
|
-
if (block.type === "
|
|
11513
|
-
|
|
11514
|
-
if (block.type === "
|
|
11515
|
-
|
|
12243
|
+
let rendered;
|
|
12244
|
+
if (block.type === "doctor-report") rendered = buildDoctorReport();
|
|
12245
|
+
else if (block.type === "tiles") rendered = buildTiles(block);
|
|
12246
|
+
else if (block.type === "flow") rendered = buildFlow(block);
|
|
12247
|
+
else if (block.type === "stacks") rendered = buildStacks(block);
|
|
12248
|
+
else if (block.type === "hooks") rendered = buildHooks(block);
|
|
12249
|
+
else if (block.type === "callout") rendered = buildCallout(block);
|
|
12250
|
+
else if (block.type === "table") rendered = buildTable(block);
|
|
12251
|
+
else if (block.type === "tabs") rendered = buildTabs(block);
|
|
12252
|
+
else if (block.type === "checklist") rendered = buildChecklist(block);
|
|
12253
|
+
else if (block.type === "plugins-live") {
|
|
12254
|
+
rendered = buildPluginsLive(block);
|
|
12255
|
+
} else if (block.type === "staff") rendered = buildStaff(block);
|
|
12256
|
+
else if (block.type === "environments") rendered = buildEnvironments();
|
|
12257
|
+
else if (block.type === "intake") rendered = buildIntake(block);
|
|
12258
|
+
else if (!block.type) rendered = buildCard(block);
|
|
12259
|
+
else throw new TypeError("unsupported renderer type " + block.type);
|
|
12260
|
+
return markRenderedValue(rendered, block);
|
|
11516
12261
|
}
|
|
11517
12262
|
|
|
11518
12263
|
/* The readiness intake: one flat, answerable question per row, grouped by
|
|
@@ -11734,6 +12479,7 @@
|
|
|
11734
12479
|
|
|
11735
12480
|
function renderAll() {
|
|
11736
12481
|
syncHealthViewModel();
|
|
12482
|
+
refreshCatalogAudit();
|
|
11737
12483
|
$main.innerHTML = "";
|
|
11738
12484
|
$nav.innerHTML = "";
|
|
11739
12485
|
ctlSeq = 0;
|
|
@@ -11760,7 +12506,25 @@
|
|
|
11760
12506
|
if (section.src)
|
|
11761
12507
|
head.appendChild(el("span", "src-chip", "⚙ " + esc(section.src)));
|
|
11762
12508
|
sec.appendChild(head);
|
|
11763
|
-
section.blocks.
|
|
12509
|
+
if (section.blocks.length === 0) {
|
|
12510
|
+
const empty = el(
|
|
12511
|
+
"div",
|
|
12512
|
+
"callout section-empty",
|
|
12513
|
+
lisaLiveMode
|
|
12514
|
+
? "No live value is available for this section."
|
|
12515
|
+
: "No values are available for this section."
|
|
12516
|
+
);
|
|
12517
|
+
empty.dataset.testid = "section-empty";
|
|
12518
|
+
empty.dataset.state = lisaLiveMode ? "unknown" : "empty";
|
|
12519
|
+
sec.appendChild(empty);
|
|
12520
|
+
} else {
|
|
12521
|
+
section.blocks.forEach(b => sec.appendChild(buildBlock(b)));
|
|
12522
|
+
}
|
|
12523
|
+
sec.dataset.liveState = lisaLiveMode
|
|
12524
|
+
? section.blocks.length === 0
|
|
12525
|
+
? "empty"
|
|
12526
|
+
: "live"
|
|
12527
|
+
: "demo";
|
|
11764
12528
|
$main.appendChild(sec);
|
|
11765
12529
|
});
|
|
11766
12530
|
}
|
|
@@ -11794,65 +12558,31 @@
|
|
|
11794
12558
|
const q = (query || "").trim().toLowerCase();
|
|
11795
12559
|
const sec = document.getElementById("section-" + activeSection);
|
|
11796
12560
|
if (!sec) return;
|
|
11797
|
-
sec
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
/* --- live-config hydration (lisa ui injects window.LISA_LIVE_CONFIG) --- */
|
|
11806
|
-
function walkRows(blocks, fn) {
|
|
11807
|
-
(blocks || []).forEach(block => {
|
|
11808
|
-
(block.rows || []).forEach(fn);
|
|
11809
|
-
(block.tabs || []).forEach(tab => walkRows(tab.blocks, fn));
|
|
12561
|
+
const existing = sec.querySelector(".filter-empty");
|
|
12562
|
+
if (existing) existing.remove();
|
|
12563
|
+
const candidates = [
|
|
12564
|
+
...sec.querySelectorAll(".row, tbody tr, .stack-card, .hook-card"),
|
|
12565
|
+
];
|
|
12566
|
+
candidates.forEach(node => {
|
|
12567
|
+
node.style.display =
|
|
12568
|
+
!q || node.textContent.toLowerCase().includes(q) ? "" : "none";
|
|
11810
12569
|
});
|
|
11811
|
-
|
|
11812
|
-
|
|
11813
|
-
|
|
11814
|
-
.
|
|
11815
|
-
|
|
11816
|
-
|
|
11817
|
-
|
|
11818
|
-
|
|
11819
|
-
|
|
11820
|
-
obj
|
|
12570
|
+
if (
|
|
12571
|
+
q &&
|
|
12572
|
+
candidates.length > 0 &&
|
|
12573
|
+
candidates.every(node => node.style.display === "none")
|
|
12574
|
+
) {
|
|
12575
|
+
const empty = el(
|
|
12576
|
+
"div",
|
|
12577
|
+
"callout filter-empty",
|
|
12578
|
+
"No live or static values match this filter."
|
|
11821
12579
|
);
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
if (control.type === "toggle") {
|
|
11825
|
-
control.value = !!value;
|
|
11826
|
-
return;
|
|
11827
|
-
}
|
|
11828
|
-
if (control.type === "number" && typeof value === "number") {
|
|
11829
|
-
control.value = value;
|
|
11830
|
-
return;
|
|
11831
|
-
}
|
|
11832
|
-
if (control.type === "text" || control.type === "select") {
|
|
11833
|
-
control.value = String(value);
|
|
11834
|
-
return;
|
|
11835
|
-
}
|
|
11836
|
-
if (control.type === "tags" && Array.isArray(value)) {
|
|
11837
|
-
control.values = value.map(String);
|
|
11838
|
-
return;
|
|
11839
|
-
}
|
|
11840
|
-
if (control.type === "envmap") {
|
|
11841
|
-
if (typeof value === "string") {
|
|
11842
|
-
control.production = value;
|
|
11843
|
-
return;
|
|
11844
|
-
}
|
|
11845
|
-
if (value && typeof value === "object") {
|
|
11846
|
-
["dev", "staging", "production"].forEach(envName => {
|
|
11847
|
-
if (value[envName] !== undefined)
|
|
11848
|
-
control[envName] = String(value[envName]);
|
|
11849
|
-
});
|
|
11850
|
-
}
|
|
11851
|
-
return;
|
|
12580
|
+
empty.dataset.state = lisaLiveMode ? "unknown" : "empty";
|
|
12581
|
+
sec.appendChild(empty);
|
|
11852
12582
|
}
|
|
11853
|
-
if (control.type === "static" && typeof value === "string")
|
|
11854
|
-
control.value = value;
|
|
11855
12583
|
}
|
|
12584
|
+
|
|
12585
|
+
/* --- live-config hydration (lisa ui injects window.LISA_LIVE_CONFIG) --- */
|
|
11856
12586
|
const REMOTE_AGENT_ORDER = [
|
|
11857
12587
|
"claude",
|
|
11858
12588
|
"codex",
|
|
@@ -11918,22 +12648,32 @@
|
|
|
11918
12648
|
DATA.sections.remote.blocks[1].rows = startupRows;
|
|
11919
12649
|
}
|
|
11920
12650
|
function hydrateFromLiveConfig() {
|
|
11921
|
-
|
|
11922
|
-
|
|
11923
|
-
|
|
11924
|
-
|
|
11925
|
-
|
|
11926
|
-
const value = liveGet(cfg, row.key);
|
|
11927
|
-
if (value !== undefined) hydrateControl(row.control, value);
|
|
11928
|
-
});
|
|
11929
|
-
});
|
|
12651
|
+
lisaLiveMode = Object.hasOwn(window, "LISA_LIVE_CONFIG");
|
|
12652
|
+
const cfg = lisaLiveMode ? window.LISA_LIVE_CONFIG : {};
|
|
12653
|
+
lisaCatalogAudit = auditUiCatalog(DATA);
|
|
12654
|
+
if (lisaLiveMode) {
|
|
12655
|
+
lisaCatalogPreparation = prepareUiCatalogForLive(DATA, cfg);
|
|
11930
12656
|
markObservabilityProvidersPending();
|
|
12657
|
+
configureRemoteEnvironment(cfg);
|
|
12658
|
+
} else {
|
|
12659
|
+
lisaCatalogPreparation = null;
|
|
11931
12660
|
}
|
|
11932
|
-
configureRemoteEnvironment(cfg);
|
|
11933
12661
|
const repoEl = document.querySelector(".project-switch .repo");
|
|
11934
|
-
if (
|
|
12662
|
+
if (
|
|
12663
|
+
repoEl &&
|
|
12664
|
+
cfg &&
|
|
12665
|
+
typeof cfg === "object" &&
|
|
12666
|
+
cfg.github &&
|
|
12667
|
+
cfg.github.org &&
|
|
12668
|
+
cfg.github.repo
|
|
12669
|
+
) {
|
|
11935
12670
|
repoEl.textContent = cfg.github.org + "/" + cfg.github.repo;
|
|
11936
12671
|
}
|
|
12672
|
+
window.LISA_UI_CATALOG_STATE = {
|
|
12673
|
+
liveMode: lisaLiveMode,
|
|
12674
|
+
audit: lisaCatalogAudit,
|
|
12675
|
+
preparation: lisaCatalogPreparation,
|
|
12676
|
+
};
|
|
11937
12677
|
}
|
|
11938
12678
|
|
|
11939
12679
|
/* --- live-status hydration (same-origin GET /api/status) --- */
|
|
@@ -12002,6 +12742,7 @@
|
|
|
12002
12742
|
function setProviderStatusCell(provider, cell) {
|
|
12003
12743
|
const block = connectedProvidersBlock();
|
|
12004
12744
|
if (!block || !Array.isArray(block.rows)) return;
|
|
12745
|
+
cell.liveSource = block.liveSource;
|
|
12005
12746
|
block.rows.forEach(row => {
|
|
12006
12747
|
if (!Array.isArray(row)) return;
|
|
12007
12748
|
row.forEach((entry, index) => {
|
|
@@ -12010,10 +12751,14 @@
|
|
|
12010
12751
|
}
|
|
12011
12752
|
});
|
|
12012
12753
|
});
|
|
12754
|
+
refreshCatalogAudit();
|
|
12013
12755
|
const td = document.querySelector(
|
|
12014
12756
|
'td[data-provider="' + provider + '"]'
|
|
12015
12757
|
);
|
|
12016
|
-
if (td)
|
|
12758
|
+
if (td) {
|
|
12759
|
+
paintProviderStatus(td, cell);
|
|
12760
|
+
markRenderedValue(td, cell);
|
|
12761
|
+
}
|
|
12017
12762
|
}
|
|
12018
12763
|
function markObservabilityProvidersPending() {
|
|
12019
12764
|
["sentry", "cloudwatch-alarms", "x-ray"].forEach(provider => {
|
|
@@ -12413,6 +13158,7 @@
|
|
|
12413
13158
|
/** Replace only the existing Health results card after state changes. */
|
|
12414
13159
|
function repaintHealthResults() {
|
|
12415
13160
|
syncHealthViewModel();
|
|
13161
|
+
refreshCatalogAudit();
|
|
12416
13162
|
const block = healthResultsBlock();
|
|
12417
13163
|
const current = document.getElementById("healthResults");
|
|
12418
13164
|
if (block && current) current.replaceWith(buildTable(block));
|
|
@@ -12708,6 +13454,7 @@
|
|
|
12708
13454
|
rows: [
|
|
12709
13455
|
{
|
|
12710
13456
|
key: "",
|
|
13457
|
+
staticCopy: "fixed supported repository host",
|
|
12711
13458
|
label: "Git host",
|
|
12712
13459
|
desc: "Which host this repository lives on. GitHub is the only host Lisa provisions today.",
|
|
12713
13460
|
control: sel("GitHub", ["GitHub", "GitLab", "Bitbucket"]),
|
|
@@ -12716,66 +13463,82 @@
|
|
|
12716
13463
|
};
|
|
12717
13464
|
}
|
|
12718
13465
|
function githubRepoSettingsRows(settings) {
|
|
13466
|
+
const setting = (name, render) =>
|
|
13467
|
+
Object.hasOwn(settings, name)
|
|
13468
|
+
? render(settings[name])
|
|
13469
|
+
: stat("unknown", "warn");
|
|
12719
13470
|
return [
|
|
12720
13471
|
{
|
|
12721
13472
|
label: "Allow merge commits",
|
|
12722
13473
|
desc: "The fleet is merge-only — squash and rebase are disabled because squashing flattens release commits and breaks promotion.",
|
|
12723
|
-
control:
|
|
13474
|
+
control: setting("allow_merge_commit", value =>
|
|
13475
|
+
tog(Boolean(value))
|
|
13476
|
+
),
|
|
12724
13477
|
},
|
|
12725
13478
|
{
|
|
12726
13479
|
label: "Allow squash merge",
|
|
12727
13480
|
desc: "",
|
|
12728
|
-
control:
|
|
13481
|
+
control: setting("allow_squash_merge", value =>
|
|
13482
|
+
tog(Boolean(value))
|
|
13483
|
+
),
|
|
12729
13484
|
},
|
|
12730
13485
|
{
|
|
12731
13486
|
label: "Allow rebase merge",
|
|
12732
13487
|
desc: "",
|
|
12733
|
-
control:
|
|
13488
|
+
control: setting("allow_rebase_merge", value =>
|
|
13489
|
+
tog(Boolean(value))
|
|
13490
|
+
),
|
|
12734
13491
|
},
|
|
12735
13492
|
{
|
|
12736
13493
|
label: "Auto-merge",
|
|
12737
13494
|
desc: "Lisa PRs enable auto-merge and drive themselves to green.",
|
|
12738
|
-
control: tog(
|
|
13495
|
+
control: setting("allow_auto_merge", value => tog(Boolean(value))),
|
|
12739
13496
|
},
|
|
12740
13497
|
{
|
|
12741
13498
|
label: "Suggest updating PR branches",
|
|
12742
13499
|
desc: "",
|
|
12743
|
-
control:
|
|
13500
|
+
control: setting("allow_update_branch", value =>
|
|
13501
|
+
tog(Boolean(value))
|
|
13502
|
+
),
|
|
12744
13503
|
},
|
|
12745
13504
|
{
|
|
12746
13505
|
label: "Delete head branch on merge",
|
|
12747
13506
|
desc: "Environment branches survive via the prevent-delete ruleset.",
|
|
12748
|
-
control:
|
|
13507
|
+
control: setting("delete_branch_on_merge", value =>
|
|
13508
|
+
tog(Boolean(value))
|
|
13509
|
+
),
|
|
12749
13510
|
},
|
|
12750
13511
|
{
|
|
12751
13512
|
label: "Merge commit title",
|
|
12752
13513
|
desc: "",
|
|
12753
|
-
control:
|
|
12754
|
-
"
|
|
12755
|
-
|
|
12756
|
-
|
|
13514
|
+
control: setting("merge_commit_title", value =>
|
|
13515
|
+
sel(value === null ? "" : String(value), [
|
|
13516
|
+
"MERGE_MESSAGE",
|
|
13517
|
+
"PR_TITLE",
|
|
13518
|
+
])
|
|
13519
|
+
),
|
|
12757
13520
|
},
|
|
12758
13521
|
{
|
|
12759
13522
|
label: "Issues tab",
|
|
12760
13523
|
desc: "",
|
|
12761
|
-
control: tog(
|
|
13524
|
+
control: setting("has_issues", value => tog(Boolean(value))),
|
|
12762
13525
|
},
|
|
12763
13526
|
{
|
|
12764
13527
|
label: "Wiki tab",
|
|
12765
13528
|
desc: "Off — Lisa uses the in-repo wiki/ directory instead.",
|
|
12766
|
-
control: tog(
|
|
13529
|
+
control: setting("has_wiki", value => tog(Boolean(value))),
|
|
12767
13530
|
},
|
|
12768
13531
|
{
|
|
12769
13532
|
label: "Secret scanning + push protection",
|
|
12770
13533
|
desc: "Best-effort; skipped when the plan lacks GitHub Advanced Security.",
|
|
12771
|
-
control: tog(
|
|
13534
|
+
control: setting("secret_scanning", value => tog(Boolean(value))),
|
|
12772
13535
|
},
|
|
12773
13536
|
{
|
|
12774
13537
|
label: "Default branch",
|
|
12775
13538
|
desc: "Live default branch from the repository.",
|
|
12776
|
-
control:
|
|
12777
|
-
mono: true
|
|
12778
|
-
|
|
13539
|
+
control: setting("default_branch", value =>
|
|
13540
|
+
txt(value === null ? "" : String(value), { mono: true })
|
|
13541
|
+
),
|
|
12779
13542
|
},
|
|
12780
13543
|
];
|
|
12781
13544
|
}
|
|
@@ -12790,6 +13553,7 @@
|
|
|
12790
13553
|
host,
|
|
12791
13554
|
{
|
|
12792
13555
|
type: "callout",
|
|
13556
|
+
liveSource: "api:github-repo",
|
|
12793
13557
|
variant: "warn",
|
|
12794
13558
|
text:
|
|
12795
13559
|
"<b>unknown — not authenticated / unavailable</b> — " +
|
|
@@ -12808,6 +13572,7 @@
|
|
|
12808
13572
|
host,
|
|
12809
13573
|
{
|
|
12810
13574
|
card: "Repository settings",
|
|
13575
|
+
liveSource: "api:github-repo",
|
|
12811
13576
|
note:
|
|
12812
13577
|
"Live from gh api repos/" +
|
|
12813
13578
|
String(value.owner || "") +
|
|
@@ -12817,9 +13582,15 @@
|
|
|
12817
13582
|
},
|
|
12818
13583
|
{
|
|
12819
13584
|
type: "table",
|
|
13585
|
+
id: "github-rulesets",
|
|
13586
|
+
liveSource: "api:github-repo",
|
|
12820
13587
|
card: "Rulesets",
|
|
12821
13588
|
note: "Live from gh api repos/{owner}/{repo}/rulesets",
|
|
12822
13589
|
cols: ["Ruleset", "Applies to", "Enforces", "Active"],
|
|
13590
|
+
empty: {
|
|
13591
|
+
state: "empty",
|
|
13592
|
+
message: "No live rulesets were reported.",
|
|
13593
|
+
},
|
|
12823
13594
|
rows: rulesets.map(row => [
|
|
12824
13595
|
{ t: "mono", v: String(row.name || "") },
|
|
12825
13596
|
{ t: "text", v: String(row.appliesTo || "—") },
|
|
@@ -12833,9 +13604,15 @@
|
|
|
12833
13604
|
},
|
|
12834
13605
|
{
|
|
12835
13606
|
type: "table",
|
|
13607
|
+
id: "github-labels",
|
|
13608
|
+
liveSource: "api:github-repo",
|
|
12836
13609
|
card: "Lifecycle labels",
|
|
12837
13610
|
note: "Config-expected labels vs live gh label list",
|
|
12838
13611
|
cols: ["Label", "Role", "Color", "Status"],
|
|
13612
|
+
empty: {
|
|
13613
|
+
state: "empty",
|
|
13614
|
+
message: "No live lifecycle labels were reported.",
|
|
13615
|
+
},
|
|
12839
13616
|
rows: labels.map(row => [
|
|
12840
13617
|
{ t: "mono", v: String(row.name || "") },
|
|
12841
13618
|
{ t: "text", v: String(row.role || "") },
|
|
@@ -12854,9 +13631,15 @@
|
|
|
12854
13631
|
},
|
|
12855
13632
|
{
|
|
12856
13633
|
type: "table",
|
|
13634
|
+
id: "github-secrets",
|
|
13635
|
+
liveSource: "api:github-repo",
|
|
12857
13636
|
card: "Secrets",
|
|
12858
13637
|
note: "Presence only from gh secret list — values are never read",
|
|
12859
13638
|
cols: ["Secret", "Purpose", "Status"],
|
|
13639
|
+
empty: {
|
|
13640
|
+
state: "empty",
|
|
13641
|
+
message: "No live secret-presence rows were reported.",
|
|
13642
|
+
},
|
|
12860
13643
|
rows: secrets.map(row => [
|
|
12861
13644
|
{ t: "mono", v: String(row.name || "") },
|
|
12862
13645
|
{ t: "text", v: String(row.purpose || "") },
|
|
@@ -12965,6 +13748,7 @@
|
|
|
12965
13748
|
],
|
|
12966
13749
|
];
|
|
12967
13750
|
}
|
|
13751
|
+
refreshCatalogAudit();
|
|
12968
13752
|
const existing = document.getElementById("deploy-pipeline-stages");
|
|
12969
13753
|
if (existing) {
|
|
12970
13754
|
existing.replaceWith(buildTable(block));
|
|
@@ -13175,8 +13959,10 @@
|
|
|
13175
13959
|
showSection(location.hash ? location.hash.slice(1) : "overview");
|
|
13176
13960
|
if (!document.getElementById("section-" + activeSection))
|
|
13177
13961
|
showSection("overview");
|
|
13178
|
-
|
|
13179
|
-
|
|
13962
|
+
if (lisaLiveMode) {
|
|
13963
|
+
void hydrateLiveStatuses();
|
|
13964
|
+
void hydrateHealth();
|
|
13965
|
+
}
|
|
13180
13966
|
}
|
|
13181
13967
|
if (document.readyState === "loading") {
|
|
13182
13968
|
document.addEventListener("DOMContentLoaded", boot);
|