@bongos/core 1.19.1007 → 1.19.1009
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/.bongos-core.json +41 -21
- package/docs/copy-inventory.md +38 -36
- package/docs/copy-registry.json +74 -56
- package/docs/module-api-changelog.md +4 -0
- package/modules/hall-ui/public/idea.js +2 -2
- package/modules/hall-ui/public/ideas.css +21 -0
- package/modules/hall-ui/public/ideas.html +59 -33
- package/modules/hall-ui/public/ideas.js +128 -17
- package/modules/hall-ui/public/ideas.states.json +3 -2
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/fixtures/ideator-prefold/README.md +36 -0
- package/tests/fixtures/ideator-prefold/ideas.html.baseline +210 -0
- package/tests/fixtures/ideator-prefold/ideas.js.baseline +962 -0
- package/tests/hall_record_world.mjs +24 -3
- package/tests/idea_spark_hall.mjs +195 -4
- package/tests/ideator_one_line_path_proof.mjs +338 -0
|
@@ -264,7 +264,11 @@
|
|
|
264
264
|
|
|
265
265
|
renderRecommendation(draft);
|
|
266
266
|
|
|
267
|
-
|
|
267
|
+
formRendered = true;
|
|
268
|
+
// The meter is unhidden through the disclosure, which owns the rule for both
|
|
269
|
+
// directions; setting it here as well would be a second source of truth for
|
|
270
|
+
// the same visibility and is what let the reopen case rot unnoticed.
|
|
271
|
+
setDisclosure(fullOpen);
|
|
268
272
|
renderMeterSkeleton();
|
|
269
273
|
}
|
|
270
274
|
|
|
@@ -902,24 +906,78 @@
|
|
|
902
906
|
return true;
|
|
903
907
|
}
|
|
904
908
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
909
|
+
// ---------------------------------------------------------------------------
|
|
910
|
+
// the disclosure (task 1004087 / spec R04, decision 18)
|
|
911
|
+
// ---------------------------------------------------------------------------
|
|
912
|
+
//
|
|
913
|
+
// THE FULL IDEA FORM IS CLOSED UNTIL SOMEONE ASKS FOR IT, and this is the
|
|
914
|
+
// whole point of the fold. Decision 18 put the five questions on the same
|
|
915
|
+
// surface as the one-line composer and named that the riskiest call in the
|
|
916
|
+
// spec: a spark's value is that it costs nothing to have, and a five-field
|
|
917
|
+
// form standing permanently open under the fast path is exactly how a page
|
|
918
|
+
// starts feeling like something to be filled in.
|
|
919
|
+
//
|
|
920
|
+
// So "on the same surface" is spent on ONE press, and nothing else. While the
|
|
921
|
+
// form is shut this page does not fetch the template, does not wire the
|
|
922
|
+
// per-keystroke scorer, and does not hold the rail reserve open — the one-line
|
|
923
|
+
// path is the first thing under the heading at full width, and it is ready
|
|
924
|
+
// before any of the Full Idea machinery has run.
|
|
925
|
+
//
|
|
926
|
+
// Closing again HIDES, it does not discard: the draft lives in localStorage
|
|
927
|
+
// exactly as it did before, so the toggle is reversible in both directions and
|
|
928
|
+
// the button can say so without lying.
|
|
929
|
+
|
|
930
|
+
let fullBooted = false; // the template read was STARTED once
|
|
931
|
+
let formRendered = false; // ...and actually produced a form (and so a meter)
|
|
932
|
+
let fullOpen = false;
|
|
933
|
+
|
|
934
|
+
// The rail reserve belongs to the meter, so it arrives and leaves with it.
|
|
935
|
+
// style.css reserves `--rail-right-w` from `.app-content--railed > main` at the
|
|
936
|
+
// wide breakpoint whether or not anything is in the rail, which is why this is
|
|
937
|
+
// a class toggle and not simply `#fi-meter.hidden`.
|
|
938
|
+
function setRail(on) {
|
|
939
|
+
const c = document.getElementById('fi-content');
|
|
940
|
+
if (!c || !c.classList) return;
|
|
941
|
+
if (on) c.classList.add('app-content--railed');
|
|
942
|
+
else c.classList.remove('app-content--railed');
|
|
943
|
+
}
|
|
913
944
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
945
|
+
function setDisclosure(open) {
|
|
946
|
+
fullOpen = open;
|
|
947
|
+
const form = $('#fi-form');
|
|
948
|
+
const btn = $('#fi-open');
|
|
949
|
+
const note = $('#fi-disclose-note');
|
|
950
|
+
if (form) form.hidden = !open;
|
|
951
|
+
if (btn) {
|
|
952
|
+
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
953
|
+
btn.textContent = open ? 'Put the five questions away' : 'Answer the five questions';
|
|
954
|
+
}
|
|
955
|
+
if (note) {
|
|
956
|
+
note.textContent = open
|
|
957
|
+
? 'Putting them away keeps your draft — it is saved as you type.'
|
|
958
|
+
: 'Nothing you have typed above is lost by opening this.';
|
|
922
959
|
}
|
|
960
|
+
// The meter is the form's readout, so it follows the form BOTH WAYS — and the
|
|
961
|
+
// condition is written as one expression rather than a hide-only branch
|
|
962
|
+
// because the hide-only version was a real bug: `renderForm` is the only
|
|
963
|
+
// thing that ever unhid the meter, `bootFullIdea` runs it at most once, so
|
|
964
|
+
// opening, closing and opening again brought the form and the rail back with
|
|
965
|
+
// the readout gone for good — the rail reserve held open for a panel that
|
|
966
|
+
// was no longer there, which is the exact anti-pattern the fold removes.
|
|
967
|
+
//
|
|
968
|
+
// `formRendered`, not `fullBooted`: the boot flag is set BEFORE the template
|
|
969
|
+
// read, so it is true even when that read failed and there is no meter to
|
|
970
|
+
// show. An empty meter is a readout claiming a score of nothing.
|
|
971
|
+
const meter = $('#fi-meter');
|
|
972
|
+
if (meter) meter.hidden = !(open && formRendered);
|
|
973
|
+
setRail(open);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Everything the Full Idea half needs, run at most once and never on the fast
|
|
977
|
+
// path. Returns nothing: its failures are stated on the page, as before.
|
|
978
|
+
async function bootFullIdea() {
|
|
979
|
+
if (fullBooted) return;
|
|
980
|
+
fullBooted = true;
|
|
923
981
|
|
|
924
982
|
const r = await api.inbox.getInboxTemplate({});
|
|
925
983
|
if (!r.ok || !r.data || !r.data.template) {
|
|
@@ -958,5 +1016,58 @@
|
|
|
958
1016
|
}
|
|
959
1017
|
}
|
|
960
1018
|
|
|
1019
|
+
async function openFullIdea() {
|
|
1020
|
+
setDisclosure(true);
|
|
1021
|
+
await bootFullIdea();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
async function toggleFullIdea() {
|
|
1025
|
+
if (fullOpen) { setDisclosure(false); return; }
|
|
1026
|
+
await openFullIdea();
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
async function main() {
|
|
1030
|
+
// THE QUEUE IS LOADED FIRST, AND UNCONDITIONALLY. The composer and the spark
|
|
1031
|
+
// queue are independent surfaces that happen to share a page: the queue needs
|
|
1032
|
+
// no template. Loading it after the template check meant one failed fetch took
|
|
1033
|
+
// BOTH down — the reported symptom was "Could not load the idea template" AND a
|
|
1034
|
+
// queue stuck on "Loading…" forever, which read as two bugs and was one.
|
|
1035
|
+
// A surface that does not depend on the failure must not be hostage to it.
|
|
1036
|
+
loadQueue();
|
|
1037
|
+
|
|
1038
|
+
// Quick filing (R08) is a THIRD independent surface on this page, offered
|
|
1039
|
+
// only outside develop mode — developing a spark always produces a Full
|
|
1040
|
+
// Idea, never a fresh quick report (see the section's own header comment).
|
|
1041
|
+
if (DEVELOP_ID) {
|
|
1042
|
+
const scroll = $('#fq-compose-scroll');
|
|
1043
|
+
if (scroll) scroll.hidden = true;
|
|
1044
|
+
} else {
|
|
1045
|
+
bootQuickComposer();
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
const openBtn = $('#fi-open');
|
|
1049
|
+
if (openBtn) openBtn.addEventListener('click', toggleFullIdea);
|
|
1050
|
+
setDisclosure(false);
|
|
1051
|
+
|
|
1052
|
+
// THE THREE CASES A SHUT FORM WOULD STRAND (task 1004087 / spec R04). The
|
|
1053
|
+
// done-when is "no dead links", and a link that lands on a page whose
|
|
1054
|
+
// relevant half is folded away is dead in the only sense that matters to
|
|
1055
|
+
// whoever followed it:
|
|
1056
|
+
//
|
|
1057
|
+
// ?develop=<id> the spark queue's own rows, and /idea/:id — the whole
|
|
1058
|
+
// request is "develop this", so arriving at a shut form
|
|
1059
|
+
// would be the page ignoring it.
|
|
1060
|
+
// #full the deep link anything else uses to mean "the composer",
|
|
1061
|
+
// including /idea/:id's fallback action.
|
|
1062
|
+
// a saved draft half-written work already in localStorage. Leaving it
|
|
1063
|
+
// behind a press nobody knows to make is how "a partial
|
|
1064
|
+
// draft is not lost on navigation" (R152) quietly stops
|
|
1065
|
+
// being true — the draft would still be there and no one
|
|
1066
|
+
// would ever see it again.
|
|
1067
|
+
if (DEVELOP_ID || location.hash === '#full' || readDraft()) {
|
|
1068
|
+
await openFullIdea();
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
961
1072
|
main();
|
|
962
1073
|
})();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_": "The hall's IDEA COMPOSER through modules/ui-design/kit/render.js (task 1003305). The five questions are rendered from GET /inbox/template and the spark queue from GET /inbox/sparks, so the populated form needs the hall-preview harness; the kit's own stub answers neither and renders the honest `could not load the idea template` line instead, which is the `no-template` state below.\n\n node scripts/hall-preview/server.js --port 4612 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/ideas.states.json --base-in http://builders.localhost:4612 --base-out http://builders.localhost:4612\n\
|
|
2
|
+
"_": "The hall's IDEA COMPOSER through modules/ui-design/kit/render.js (task 1003305). The five questions are rendered from GET /inbox/template and the spark queue from GET /inbox/sparks, so the populated form needs the hall-preview harness; the kit's own stub answers neither and renders the honest `could not load the idea template` line instead, which is the `no-template` state below.\n\n node scripts/hall-preview/server.js --port 4612 --fixture-me\n node modules/ui-design/kit/render.js --states modules/hall-ui/public/ideas.states.json --base-in http://builders.localhost:4612 --base-out http://builders.localhost:4612\n\nTHE FOLD (task 1004087 / spec R04) is why there are now three states rather than two. `compose` is what a builder actually lands on — the one-line composer at the top, the Full Idea form folded away behind one press, and NO rail reserve, which is the whole reason that state is rendered at 1440 as well as at 390/320. `full` is the same page one press later: the five questions, the meter, and the rail. Rendering only one of them would measure half the page, and the half that is measured on every arrival is the first.",
|
|
3
3
|
"page": "/ideas",
|
|
4
4
|
"surface": "hall-ui",
|
|
5
5
|
"stub": { "prefix": "/builders/" },
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"_console": "The 501 is the harness, not the page: the meter re-scores through POST /inbox/score on every keystroke and scripts/hall-preview/server.js answers every write with preview_readonly by design. Declared rather than hidden.",
|
|
9
9
|
"ignoreConsole": ["/api/(gds|bongos)/", "\\b401\\b", "\\b404\\b", "\\b501\\b", "Unauthorized", "Not Found", "Not Implemented", "load failed"],
|
|
10
10
|
"states": {
|
|
11
|
-
"compose": { "auth": true, "actions": [["wait", 700]], "expect": { "visible": ["#
|
|
11
|
+
"compose": { "auth": true, "actions": [["wait", 700]], "expect": { "visible": ["#fq-form", "#fi-queue", "#fi-open"] } },
|
|
12
|
+
"full": { "auth": true, "actions": [["wait", 700], ["click", "#fi-open"], ["wait", 700]], "expect": { "visible": ["#fi-form", "#fi-fields", "#fi-meter"] } },
|
|
12
13
|
"develop": { "auth": true, "url": "/ideas?develop=1000884", "actions": [["wait", 700]], "expect": { "visible": ["#fi-develop"] } }
|
|
13
14
|
},
|
|
14
15
|
"reducedMotion": { "auth": true, "actions": [["wait", 700]] }
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1009",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.1009",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.1009",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.1009'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# The pre-fold ideas page — a frozen baseline
|
|
2
|
+
|
|
3
|
+
Byte copies of `modules/hall-ui/public/ideas.html` and `ideas.js` as they stood at commit
|
|
4
|
+
**`f375a1f9`**, the commit immediately before the Full Idea form was folded behind a
|
|
5
|
+
disclosure (task [#1004087](https://cloudbongos.com/builders#/task/1004087), spec R04).
|
|
6
|
+
|
|
7
|
+
They exist so `tests/ideator_one_line_path_proof.mjs` can measure the current filing path
|
|
8
|
+
against **the page that actually existed**, rather than against a description of it. A
|
|
9
|
+
described baseline agrees with the current page because whoever wrote it made it agree;
|
|
10
|
+
these can only agree if the code does.
|
|
11
|
+
|
|
12
|
+
Re-cut them to check they have not drifted:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
git show f375a1f9:modules/hall-ui/public/ideas.js > tests/fixtures/ideator-prefold/ideas.js.baseline
|
|
16
|
+
git show f375a1f9:modules/hall-ui/public/ideas.html > tests/fixtures/ideator-prefold/ideas.html.baseline
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Do not edit these, and do not "fix" them
|
|
20
|
+
|
|
21
|
+
They are **evidence, not source**. Every finding in
|
|
22
|
+
[`docs/governance/<redacted>.md`](../../../docs/governance/<redacted>.md)
|
|
23
|
+
is a comparison against them, so editing one silently turns a measurement into a tautology.
|
|
24
|
+
The proof's last claim exists for exactly that: it asserts the baseline still fetches the
|
|
25
|
+
template unconditionally, still knows nothing about a disclosure, and is still a different
|
|
26
|
+
file from the live page — so tidying these into agreement reds the suite instead of passing
|
|
27
|
+
quietly.
|
|
28
|
+
|
|
29
|
+
## Why the `.baseline` extension
|
|
30
|
+
|
|
31
|
+
Not decoration, and not a way around a gate. `scripts/gds/fitness-ratchets.js` measures
|
|
32
|
+
cross-file duplication over `git ls-files '*.js'`, and a frozen copy of a live file is, by
|
|
33
|
+
construction, ~500 duplicate windows against it. That metric exists to find copy/paste a
|
|
34
|
+
maintainer should collapse; collapsing *this* copy would destroy the only thing it is for.
|
|
35
|
+
The extension states what the file is — recorded evidence, never loaded, never edited — and
|
|
36
|
+
keeps the duplication ratchet honest about the code it was written to police.
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Ideas — {{worldName}}</title>
|
|
7
|
+
<meta name="description" content="Write a Full Idea: five questions that turn a thought into a baseline scope of work someone else can deliver against, with a live completeness score.">
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
|
|
12
|
+
<link rel="stylesheet" href="/builders/ideas.css">
|
|
13
|
+
</head>
|
|
14
|
+
<body data-page="ideas" data-page-title="Ideas">
|
|
15
|
+
|
|
16
|
+
<div class="app">
|
|
17
|
+
<aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
|
|
18
|
+
<div class="app-scrim" id="app-scrim" hidden></div>
|
|
19
|
+
<div class="app-body">
|
|
20
|
+
<header class="app-topbar" id="app-topbar"></header>
|
|
21
|
+
<!-- --railed reserves the room the meter occupies (style.css, "the right
|
|
22
|
+
rail"). This page used to take that room by overriding
|
|
23
|
+
`.app-content > main` from its own sheet, which is a page editing shell
|
|
24
|
+
layout — invisible from the shell, and it hard-coded a topbar height. -->
|
|
25
|
+
<div class="app-content app-content--railed">
|
|
26
|
+
<!-- The page's own name, in the LABEL voice — the display face is saved for
|
|
27
|
+
section headings (the work.html precedent, task 1003304). The page had
|
|
28
|
+
no h1 at all, so it failed one-header-main-h1 on every render. -->
|
|
29
|
+
<div class="page-head page-head--board">
|
|
30
|
+
<h1 class="page-head__h">Ideas</h1>
|
|
31
|
+
<p class="page-head__sub" id="fi-sub">Loading the idea template…</p>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<main id="hall-main">
|
|
35
|
+
<!-- The spark queue (task 1002930 / R154). ABOVE the composer, because
|
|
36
|
+
"what is already waiting" is the thing to scan before starting
|
|
37
|
+
something new — and because a queue nobody sees is not a pickup
|
|
38
|
+
surface. Collapsed to a single line when empty so it never pushes the
|
|
39
|
+
composer down the page for nothing. -->
|
|
40
|
+
<section class="scroll" id="fi-queue-scroll" aria-labelledby="fi-queue-h">
|
|
41
|
+
<h2 id="fi-queue-h" class="scroll__h">Waiting to be developed</h2>
|
|
42
|
+
<p class="scroll__lede">
|
|
43
|
+
Ideas whose author has said "I had the thought but not the full picture".
|
|
44
|
+
Anyone can develop one — the author keeps the credit for having had it,
|
|
45
|
+
and you are recorded as its developer.
|
|
46
|
+
</p>
|
|
47
|
+
<div id="fi-queue"><p class="fi-note">Loading…</p></div>
|
|
48
|
+
</section>
|
|
49
|
+
|
|
50
|
+
<!-- Quick filing (task 1003069 / R08, goal 1000071 "Idea routing") —
|
|
51
|
+
the hall's first bug/idea report, separate from the Full Idea
|
|
52
|
+
composer below: title + kind + optional body + a goal picker fed
|
|
53
|
+
by the R07 suggester. A routed capture must never carry
|
|
54
|
+
grade='full' (routing.js NEVER_ROUTE_GRADE), so this cannot share
|
|
55
|
+
a submit with the form below it. Hidden while developing a spark
|
|
56
|
+
(?develop=<id>) — that surface always turns someone else's
|
|
57
|
+
existing thought into a Full Idea, never a fresh quick report. -->
|
|
58
|
+
<section class="scroll" id="fq-compose-scroll" aria-labelledby="fq-compose-h">
|
|
59
|
+
<h2 id="fq-compose-h" class="scroll__h">File a bug or idea</h2>
|
|
60
|
+
<p class="scroll__lede">
|
|
61
|
+
A quick report — title, what's going on, and where it belongs. Pick a goal
|
|
62
|
+
and it lands there as a task the moment you file; leave it on "no goal" and
|
|
63
|
+
it waits in the inbox for triage.
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
<form id="fq-form" novalidate>
|
|
67
|
+
<label class="fi-label" for="fq-title">Title</label>
|
|
68
|
+
<input type="text" id="fq-title" class="fi-input" maxlength="200" autocomplete="off"
|
|
69
|
+
placeholder="what's wrong, or what you'd like to see">
|
|
70
|
+
|
|
71
|
+
<label class="fi-label" for="fq-kind">Kind</label>
|
|
72
|
+
<!-- task 1003453: the options are FILLED from /me.task_kinds at boot
|
|
73
|
+
(ideas.js), not written here. This markup used to spell the whole
|
|
74
|
+
vocabulary and had gone stale against the DB CHECK exactly as the
|
|
75
|
+
two board filters had — a third copy nobody was watching. The one
|
|
76
|
+
option left is the degraded fallback: if the read never lands, a
|
|
77
|
+
bug can still be filed. -->
|
|
78
|
+
<select id="fq-kind" class="fi-input">
|
|
79
|
+
<option value="bug" selected>Bug</option>
|
|
80
|
+
</select>
|
|
81
|
+
|
|
82
|
+
<label class="fi-label" for="fq-body">Details <span class="fi-hint">(optional)</span></label>
|
|
83
|
+
<textarea id="fq-body" class="fi-text" rows="4"
|
|
84
|
+
placeholder="repro steps, context, anything that helps…"></textarea>
|
|
85
|
+
|
|
86
|
+
<label class="fi-label" for="fq-goal">
|
|
87
|
+
Goal
|
|
88
|
+
<span class="fi-hint">Suggested as you type; leave it on "no goal" and it waits for triage.</span>
|
|
89
|
+
</label>
|
|
90
|
+
<select id="fq-goal" class="fi-input"></select>
|
|
91
|
+
|
|
92
|
+
<!-- GRAVITY (task 1004083 / spec R02) — what will actually catch this
|
|
93
|
+
thought, stated before it is released. It sits under the picker
|
|
94
|
+
because it is the picker's consequence, and because the picker's
|
|
95
|
+
own option labels cannot carry it: "No goal (homeless)" is false
|
|
96
|
+
for a bug (ADR 0262 routes it to the maintenance goal), and a
|
|
97
|
+
named goal is false for a judgment kind (routing.js
|
|
98
|
+
NEVER_ROUTE_KINDS). The verdict comes from the server's real
|
|
99
|
+
landing matrix — see POST /inbox/preview-landing — never from a
|
|
100
|
+
second copy of the rules in ideas.js. Hidden until there is
|
|
101
|
+
something true to say. -->
|
|
102
|
+
<p class="fi-gravity" id="fq-gravity" role="status" aria-live="polite" hidden></p>
|
|
103
|
+
|
|
104
|
+
<p class="fi-err" id="fq-err" role="alert"></p>
|
|
105
|
+
<p class="fi-result" id="fq-result" role="status" aria-live="polite"></p>
|
|
106
|
+
|
|
107
|
+
<!-- The receipt that outlives the confirmation above (task 1004082 /
|
|
108
|
+
spec R01). #fq-result is the live announcement for the filing
|
|
109
|
+
just made and dies with the page; this list is the durable
|
|
110
|
+
record of the last few, read back from localStorage on load so
|
|
111
|
+
a thought filed before a reload is still traceable here. -->
|
|
112
|
+
<section class="fi-receipts" id="fq-receipts" aria-labelledby="fq-receipts-h" hidden>
|
|
113
|
+
<h3 class="fi-receipts__h" id="fq-receipts-h">Recent filings</h3>
|
|
114
|
+
<ol class="fi-receipts__list" id="fq-receipts-list"></ol>
|
|
115
|
+
<p class="fi-receipts__note" id="fq-receipts-note" hidden></p>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
<div class="fi-actions">
|
|
119
|
+
<button type="submit" class="btn" id="fq-submit">File it</button>
|
|
120
|
+
</div>
|
|
121
|
+
</form>
|
|
122
|
+
</section>
|
|
123
|
+
|
|
124
|
+
<!-- The composer. The idea detail view lives on its own page (R153). -->
|
|
125
|
+
<section class="scroll" id="fi-compose-scroll" aria-labelledby="fi-compose-h">
|
|
126
|
+
<h2 id="fi-compose-h" class="scroll__h">Write a Full Idea</h2>
|
|
127
|
+
<p class="scroll__lede">
|
|
128
|
+
A Full Idea answers five questions, so that someone else can deliver against it
|
|
129
|
+
without having to interview you first. Open to every role — the score below is
|
|
130
|
+
the only filter, and it counts substance, not who you are.
|
|
131
|
+
</p>
|
|
132
|
+
|
|
133
|
+
<form id="fi-form" novalidate>
|
|
134
|
+
<!-- DEVELOP MODE (R154): when the composer was opened from a spark,
|
|
135
|
+
this block carries the spark's own words and the title becomes a
|
|
136
|
+
read-only fact rather than a field — developing an idea must not
|
|
137
|
+
quietly rename it out from under its author. -->
|
|
138
|
+
<div class="fi-develop" id="fi-develop" hidden>
|
|
139
|
+
<p class="fi-develop__label">Developing a Big Idea Spark</p>
|
|
140
|
+
<p class="fi-develop__title" id="fi-develop-title"></p>
|
|
141
|
+
<p class="fi-develop__body" id="fi-develop-body"></p>
|
|
142
|
+
<p class="fi-develop__who" id="fi-develop-who"></p>
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
<label class="fi-label" for="fi-title" id="fi-title-label">
|
|
146
|
+
Title
|
|
147
|
+
<span class="fi-hint">One line. This is what the idea is called everywhere else.</span>
|
|
148
|
+
</label>
|
|
149
|
+
<input type="text" id="fi-title" class="fi-input" maxlength="200" autocomplete="off"
|
|
150
|
+
placeholder="what the idea is, in one line">
|
|
151
|
+
|
|
152
|
+
<!-- The five fields are rendered from GET /inbox/template — the R141
|
|
153
|
+
declaration — never hand-written here. A hard-coded form is the
|
|
154
|
+
same "second copy of the rules" defect as a hard-coded scorer,
|
|
155
|
+
one step earlier, and it would silently ignore a per-project
|
|
156
|
+
template (R156). -->
|
|
157
|
+
<div id="fi-fields" class="fi-fields">
|
|
158
|
+
<p class="fi-note">Loading the five questions…</p>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<!-- The scope recommendation (BV2.R13). Rendered from the same
|
|
162
|
+
GET /inbox/template read as the fields above, and for the same
|
|
163
|
+
reason: the two values are a closed vocabulary the database
|
|
164
|
+
enforces, so a hand-written pair of radios here would be a
|
|
165
|
+
second copy of it, free to drift. -->
|
|
166
|
+
<div id="fi-rec" class="fi-rec"></div>
|
|
167
|
+
|
|
168
|
+
<p class="fi-err" id="fi-err" role="alert"></p>
|
|
169
|
+
|
|
170
|
+
<div class="fi-actions">
|
|
171
|
+
<button type="submit" class="btn" id="fi-submit" disabled>File this idea</button>
|
|
172
|
+
<button type="button" class="btn btn--ghost" id="fi-clear">Discard draft</button>
|
|
173
|
+
<span class="fi-draft" id="fi-draft" role="status" aria-live="polite"></span>
|
|
174
|
+
</div>
|
|
175
|
+
</form>
|
|
176
|
+
</section>
|
|
177
|
+
</main>
|
|
178
|
+
|
|
179
|
+
<footer class="app-foot" id="app-foot"></footer>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<!-- The meter rides along beside the form on a wide screen and above it on a
|
|
185
|
+
narrow one (the shell's .app-rail decides). It is a READOUT, never an input:
|
|
186
|
+
every number in it comes from the server's own scorer. -->
|
|
187
|
+
<aside class="fi-meter app-rail" id="fi-meter" aria-labelledby="fi-meter-h" hidden>
|
|
188
|
+
<h2 class="fi-meter__h" id="fi-meter-h">Completeness</h2>
|
|
189
|
+
<div class="fi-gauge">
|
|
190
|
+
<div class="fi-gauge__track"><div class="fi-gauge__fill" id="fi-gauge-fill"></div></div>
|
|
191
|
+
<p class="fi-gauge__n" id="fi-gauge-n" role="status" aria-live="polite">—</p>
|
|
192
|
+
</div>
|
|
193
|
+
<ul class="fi-meter__list" id="fi-meter-list"></ul>
|
|
194
|
+
<p class="fi-meter__note">
|
|
195
|
+
Counted, not judged: each question is worth the same, and earns its share in
|
|
196
|
+
proportion to how much real text it carries. Whitespace and padding score nothing.
|
|
197
|
+
</p>
|
|
198
|
+
</aside>
|
|
199
|
+
|
|
200
|
+
<div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
|
|
201
|
+
|
|
202
|
+
<script src="/builders/shell.js?v=2026-09-22-movefeedback1"></script>
|
|
203
|
+
<script src="/builders/palette.js" defer></script>
|
|
204
|
+
<script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
|
|
205
|
+
<!-- task 1003069 (R08) — the quick-filing form's routed result links a task
|
|
206
|
+
through window.OTBTaskDetail.href(); guarded if this ever loads without it. -->
|
|
207
|
+
<script src="/builders/task-detail.js?v=2026-07-03-taskdetail2" defer></script>
|
|
208
|
+
<script src="/builders/ideas.js?v=2026-08-22-r154"></script>
|
|
209
|
+
</body>
|
|
210
|
+
</html>
|