From 76ce90fd8d39fc03415c6ace205d6f149fc2e095 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 25 Jul 2026 13:57:04 -0400 Subject: [PATCH 1/7] =?UTF-8?q?Slice=201:=20the=20interaction-totality=20m?= =?UTF-8?q?achinery=20=E2=80=94=20StateResponse,=20ResponseScope,=20family?= =?UTF-8?q?=20census?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The composition session's first slice (roadmap-workspace-remodel-plan, round 5; operator question: how do we prevent the flash class?). No new framework — the detent-table pattern generalized past transforms, as one new design module plus the dispatch instrument's rows. CONSTRUCTION — gunbc.design.state_response: a component declares its state family once (dispatch_state_family: the state-class-to-band mapping, previously spelled at THREE sites — the style's four per-state paint StaticRules, the receipt rules' four hand call sites, the base rule's inline invite vars) and each stateful channel once as a function of state. The realize folds multiply every channel across the roster: a missing family member is unwritable by totality of the fold, and joining the family is a one-row edit. DECLARATION — ChannelScope = PerStateFamily | ConstantByLaw{ruling: DeclarationRef}: the table's element type has no third arm, so the undeclared middle where the flash bug lived stops being representable. First ConstantByLaw rows: the approach ring (ruling = dispatch_brighten_neutral_note, the recorded band-neutral-emphasis ruling) and the engage sound (ruling = dispatch_sound_constant_ratchet_note, a declared ratchet with its dissolution trigger on sound_growth_trigger — never silent). CENSUS — executed over the EMITTED CSS, never only the rows: every (member x channel) cell resolves in-family or sits under a signed constant. 20 cells + 2 signed constants, counted; faults typed and located (FamilyVarUndefined | ChannelValueMissing | CrossFamilyValue{foreign_key}) per the detent-edge lesson that conflated fault kinds fail open. Shaped for later StandingIntent enrollment; the registry deliberately not built. SPECIMEN — the round-5 press fix re-derived through the fold, proven by byte-oracle: roadmap_css() emission is byte-identical (digest 50410951147178f6 unchanged, zero re-pin). Also dissolved onto the roster: the four per-state paint rules, the base rule's rest paint (state_rest_decl splices at authored positions), and the band var-name grammar (prefix from the family row, suffixes from the role enum). Receipts, all by execution on the merged tree: 8 keystones green (register/tactile/palette/page/dispatch-presentation/component-button/sandbox/ lift-parity), 5 new census witnesses green including two planted REDs — the cross-wired family locates CrossFamilyValue{fail} on the ok cells, the dropped-member family locates ChannelValueMissing on every loud cell — and the fold-totality witness (one response rule per roster member). Rider: dag/extdeps/web_audio note corrected — the 'no resume dance needed' inference was refuted by measurement (operator's ears, W1d A.2) and had been fixed in the consumer but never in the cited layer; the extdeps row now states the API fact (gesture makes resume() permitted, never unnecessary) and points at sound_preamble_note for the consumer contract. Co-Authored-By: Claude Fable 5 --- dag/extdeps/web_audio/web_audio.dag | 2 +- dag/gunbc/design/state_response.dag | 242 ++++++++++++++++++ dag/gunbc/roadmap_interaction.dag | 71 +++-- dag/gunbc/roadmap_style.dag | 100 ++++---- .../state_response_totality_witness_test.dag | 103 ++++++++ 5 files changed, 444 insertions(+), 74 deletions(-) create mode 100644 dag/gunbc/design/state_response.dag create mode 100644 dag/test/claim/state_response_totality_witness_test.dag diff --git a/dag/extdeps/web_audio/web_audio.dag b/dag/extdeps/web_audio/web_audio.dag index b6e4de3de4d..96a6421ed9c 100644 --- a/dag/extdeps/web_audio/web_audio.dag +++ b/dag/extdeps/web_audio/web_audio.dag @@ -15,7 +15,7 @@ data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { data web_audio_note: String = "Cited upstream: W3C Web Audio API. This models the small part of the API the dashboard's operation sounds actually reach — the OscillatorType enumeration, the AudioContext/OscillatorNode/GainNode interface names, and the gain-envelope method names — keeping the upstream spellings so the emitted JS is a projection of a modeled fact rather than a hand-spelled string (DESIGN section 3: cite the source, keep its real names, model what the API actually returns). It is deliberately the BOUNDED grain: not the whole graph API, only the rows a consumer needs today (DESIGN section 6, priced by displaced cost). -Two upstream facts are load-bearing for the register above it and are recorded here rather than in the consumer, because they are the API's, not ours. FIRST, gain is a linear amplitude in [0, 1] and the API's own ramp for a percussive decay is exponentialRampToValueAtTime, which cannot ramp TO zero (the spec requires a nonzero target — a zero target throws); so a decay lands on a small epsilon and the node is stopped, which is why gain_epsilon_micros exists at all and is not an arbitrary fudge. SECOND, an AudioContext created outside a user gesture starts in the 'suspended' state under every browser's autoplay policy, which is precisely why the consumer binds its tick to pointerdown: a press IS the gesture, so no resume dance and no permission prompt is needed. +Two upstream facts are load-bearing for the register above it and are recorded here rather than in the consumer, because they are the API's, not ours. FIRST, gain is a linear amplitude in [0, 1] and the API's own ramp for a percussive decay is exponentialRampToValueAtTime, which cannot ramp TO zero (the spec requires a nonzero target — a zero target throws); so a decay lands on a small epsilon and the node is stopped, which is why gain_epsilon_micros exists at all and is not an arbitrary fudge. SECOND, an AudioContext created outside a user gesture starts in the 'suspended' state under every browser's autoplay policy, and resumption is an EXPLICIT resume() call whose promise resolves when the clock actually runs — binding construction to a user gesture is what makes resume() permitted, never what makes it unnecessary. The earlier form of this sentence claimed a pointerdown-born context needs no resume dance; that inference was refuted by measurement (a fresh context can sit suspended with currentTime frozen at 0 even inside the gesture handler, so a short tick scheduled on that clock is silently swallowed — the operator's ears, remodel W1d A.2), and the consumer's contract is recorded in gunbc.roadmap_component.sound_preamble_note: gate on state, running plays immediately, anything else routes through resume().then(play). Amplitudes are carried as micros (parts per million) rather than a float because the substrate's arithmetic here is integral and a fractional literal would be a second representation of the same magnitude; wire projection divides once, at the boundary, where the API's own units demand it." diff --git a/dag/gunbc/design/state_response.dag b/dag/gunbc/design/state_response.dag new file mode 100644 index 00000000000..8f97a79aa47 --- /dev/null +++ b/dag/gunbc/design/state_response.dag @@ -0,0 +1,242 @@ +module gunbc.design.state_response + +import std.decl_ref { DeclarationRef } +import extdeps.languages.css.properties { CssPropertyName, css_property_wire } +import gunbc.design.material { + BuildRule, StaticRule, ResponseRule, SelfResponse, + MotionTrigger, TimingToken, CssDecl, decl, +} + +data state_response_note: String = "The interaction-totality law's construction half (composition slice 1; operator question round 5: how do we prevent the flash class?). The defect's mechanism: a response row's decls are CONSTANTS, so a state-dependent channel is either hand-multiplied across state selectors or accidentally constant-ized — the press fill was authored in the one channel that bypassed the detent-table pattern. This module is the detent table generalized past transforms: a component declares its STATE FAMILY once (members = binding x family key, the single authority for the state-class-to-band mapping that was previously spelled at three sites), declares each stateful CHANNEL once as a function of state (a var role, never a literal), and the realize fold multiplies the channel across every member — a missing family member is unwritable by totality of the fold, exactly as a detent edge cannot skip the table. + +The scope axis is CONSTRUCTION, not lint: a channel enters a component's table only as PerStateFamily (fold-realized) or ConstantByLaw carrying a typed DeclarationRef to the recorded ruling row (management-lane strengthening, adopted: 'who signed this constant' is a click, not archaeology). The undeclared middle — where the flash bug lived — stops being representable, because the table's element type has no third arm." + +data state_response_census_note: String = "The census half, executed over the EMITTED CSS (never only over the rows — the original defect was precisely an emission path that bypassed the table): every (family member x per-family channel) cell must resolve in-family — the member's selector carries the channel's value drawn from the member's OWN var family, and the var family is defined in the emitted root blocks — or the channel sits under a declared ConstantByLaw. Cells are counted; faults are typed and located (member key + channel property), mirroring the detent-edge lesson: an undefined var, a missing value, and a cross-family value are three faults, never one, because the first census cut that conflates fault kinds is the one that fails open. Declared limitation: cross-family detection probes at block grain, which exists only for class-bound members — a cross-wired REST member surfaces as ChannelValueMissing (still red, less located), recorded here rather than papered over. + +StandingIntent shaping (ask once, compile forever — enrollment deferred until the registry exists, deliberately not built here): the intent is 'every stateful channel of a stateful component resolves in-family or under a signed constant'; scope = the component families passed to the census (today: the dispatch instrument over roadmap_css); consumer = dag/test/claim/state_response_totality_witness_test.dag on the PR cadence; red controls = the cross-family and dropped-member planted fixtures in that file." + +type StateVarRole + = FillVar + | InkVar + | PressedVar + +fn state_var_suffix(role: StateVarRole) -> String { + match role { + FillVar => "" + InkVar => "-ink" + PressedVar => "-pressed" + } +} + +fn state_var_name(prefix: String, key: String, role: StateVarRole) -> String { + join([prefix, key, state_var_suffix(role: role)], "") +} + +fn state_var_ref(prefix: String, key: String, role: StateVarRole) -> String { + join(["var(--", state_var_name(prefix: prefix, key: key, role: role), ")"], "") +} + +type StateBinding + = RestBinding + | StateClassBinding { class_name: String } + +type StateFamilyMember { + binding: StateBinding + key: String +} + +type StateFamily { + component_class: String + var_prefix: String + members: List +} + +data member_subject_note: String = "RestBinding realizes on the component's own class selector — the rest state IS the unqualified component, which is why the base rule owns the rest paint. A class-bound member composes component class + state class, the same compound the client program flips." + +fn member_selector_text(f: StateFamily, m: StateFamilyMember) -> String { + match m.binding { + RestBinding => join([".", f.component_class], "") + StateClassBinding { class_name } => join([".", f.component_class, ".", class_name], "") + } +} + +fn member_subject(f: StateFamily, m: StateFamilyMember) -> SelectorSubject { + match m.binding { + RestBinding => ClassSelector { name: f.component_class } + StateClassBinding { class_name: _ } => RawSelector { text: member_selector_text(f: f, m: m) } + } +} + +type StateChannel + = RestChannel { prop: CssPropertyName, role: StateVarRole } + | ResponseChannel { trigger: MotionTrigger, prop: CssPropertyName, role: StateVarRole, transition_props: String } + +type ChannelScope + = PerStateFamily { channel: StateChannel } + | ConstantByLaw { channel_name: String, ruling: DeclarationRef } + +fn rest_member(f: StateFamily) -> StateFamilyMember? { + f.members |> filter(m => match m.binding { RestBinding => true StateClassBinding { class_name: _ } => false }) |> first +} + +fn member_channel_decl(f: StateFamily, m: StateFamilyMember, prop: CssPropertyName, role: StateVarRole) -> CssDecl { + decl(prop: prop, value: state_var_ref(prefix: f.var_prefix, key: m.key, role: role)) +} + +data state_rest_decl_note: String = "The rest member's channel decls, for splicing into the component's base rule at the positions its authoring order demands. Returns a LIST so an absent rest member yields an absent decl rather than a fabricated one — the absence then surfaces through the byte oracle and the census, loudly, instead of a placeholder value surviving review." + +fn state_rest_decl(f: StateFamily, prop: CssPropertyName, role: StateVarRole) -> List { + match rest_member(f: f) { + Absent => [] + Present { value: m } => [member_channel_decl(f: f, m: m, prop: prop, role: role)] + } +} + +fn scope_rest_decls(f: StateFamily, m: StateFamilyMember, channels: List) -> List { + fold(channels, init: [], f: (acc, c) => match c { + PerStateFamily { channel } => match channel { + RestChannel { prop, role } => concat(acc, [member_channel_decl(f: f, m: m, prop: prop, role: role)]) + ResponseChannel { trigger: _, prop: _, role: _, transition_props: _ } => acc + } + ConstantByLaw { channel_name: _, ruling: _ } => acc + }) +} + +data state_paint_rules_note: String = "The rest-channel realize fold: one StaticRule per class-bound member, carrying every RestChannel of the table in table order, in roster order. This is what replaces the hand-multiplied per-state paint rules — a member added to the roster gets its paint block emitted by this fold with no further authoring, and a member skipped is not writable because there is no per-member call site left to forget." + +fn state_paint_rules(f: StateFamily, channels: List) -> List { + fold(f.members, init: [], f: (acc, m) => match m.binding { + RestBinding => acc + StateClassBinding { class_name: _ } => + concat(acc, [StaticRule { selector: member_subject(f: f, m: m), decls: scope_rest_decls(f: f, m: m, channels: channels) }]) + }) +} + +data family_response_rules_note: String = "The response-channel realize fold — the press-fill law authored once as a function of state and multiplied across the family: every member (rest included — the base selector's own response) gets a ResponseRule whose response decl draws the channel's var role from the member's OWN key. Totality is the construction: the fold visits the roster, so the fault class 'one pressed var presenting as total' has no author-side spelling anymore. Timing flows in from the BehaviorRow that causes the response, exactly as detent realization takes its clock from the row rather than the table." + +fn channel_response_rules(f: StateFamily, trigger: MotionTrigger, prop: CssPropertyName, role: StateVarRole, transition_props: String, timing: TimingToken) -> List { + map(f.members, m => + ResponseRule { + subject: member_subject(f: f, m: m), + shape: SelfResponse, + trigger: trigger, + transition_props: transition_props, + timing: timing, + base_decls: [], + response_decls: [member_channel_decl(f: f, m: m, prop: prop, role: role)], + }) +} + +fn family_response_rules(f: StateFamily, channels: List, timing: TimingToken) -> List { + fold(channels, init: [], f: (acc, c) => match c { + PerStateFamily { channel } => match channel { + RestChannel { prop: _, role: _ } => acc + ResponseChannel { trigger, prop, role, transition_props } => + concat(acc, channel_response_rules(f: f, trigger: trigger, prop: prop, role: role, transition_props: transition_props, timing: timing)) + } + ConstantByLaw { channel_name: _, ruling: _ } => acc + }) +} + +type FamilyCensusFault + = FamilyVarUndefined { var_name: String } + | ChannelValueMissing + | CrossFamilyValue { foreign_key: String } + +type FamilyCensusDefect { + member_key: String + channel_prop: String + fault: FamilyCensusFault +} + +fn channel_prop_wire(c: StateChannel) -> String { + match c { + RestChannel { prop, role: _ } => css_property_wire(p: prop) + ResponseChannel { trigger: _, prop, role: _, transition_props: _ } => css_property_wire(p: prop) + } +} + +fn channel_role(c: StateChannel) -> StateVarRole { + match c { + RestChannel { prop: _, role } => role + ResponseChannel { trigger: _, prop: _, role, transition_props: _ } => role + } +} + +fn expected_cell_decl(f: StateFamily, m: StateFamilyMember, c: StateChannel) -> String { + join([channel_prop_wire(c: c), ": ", state_var_ref(prefix: f.var_prefix, key: m.key, role: channel_role(c: c))], "") +} + +fn foreign_cell_decl(f: StateFamily, foreign_key: String, c: StateChannel) -> String { + join([channel_prop_wire(c: c), ": ", state_var_ref(prefix: f.var_prefix, key: foreign_key, role: channel_role(c: c))], "") +} + +data census_block_probe_note: String = "A class-bound member's cell is probed as selector-adjacent text: the member's selector followed by a block containing the expected decl would be exact, but blocks interleave channels, so the probe pairs (selector present) with (expected decl present) and, on a miss, substitutes every other member's key into the same decl to name the foreign family it found instead. The rest member's decls live inside the component's base rule among geometry decls, so its probe is the decl alone and cross-family detection is declared out of its reach (state_response_census_note)." + +fn member_block_contains(css: String, f: StateFamily, m: StateFamilyMember, expected: String) -> Bool { + string_contains(s: css, pattern: member_selector_text(f: f, m: m)) && string_contains(s: css, pattern: expected) +} + +fn foreign_key_found(css: String, f: StateFamily, m: StateFamilyMember, c: StateChannel) -> String { + fold(f.members, init: "", f: (acc, other) => + if acc == "" && other.key != m.key && string_contains(s: css, pattern: join([member_selector_text(f: f, m: m), " { ", foreign_cell_decl(f: f, foreign_key: other.key, c: c)], "")) { + other.key + } else { + acc + }) +} + +fn cell_fault(css: String, f: StateFamily, m: StateFamilyMember, c: StateChannel) -> List { + let var_name = state_var_name(prefix: f.var_prefix, key: m.key, role: channel_role(c: c)) + if !string_contains(s: css, pattern: join(["--", var_name, ":"], "")) { + [ FamilyVarUndefined { var_name: var_name } ] + } else if member_block_contains(css: css, f: f, m: m, expected: expected_cell_decl(f: f, m: m, c: c)) { + [] + } else { + match m.binding { + RestBinding => [ ChannelValueMissing ] + StateClassBinding { class_name: _ } => { + let foreign = foreign_key_found(css: css, f: f, m: m, c: c) + if foreign == "" { + [ ChannelValueMissing ] + } else { + [ CrossFamilyValue { foreign_key: foreign } ] + } + } + } + } +} + +fn per_family_channels(channels: List) -> List { + fold(channels, init: [], f: (acc, c) => match c { + PerStateFamily { channel } => concat(acc, [channel]) + ConstantByLaw { channel_name: _, ruling: _ } => acc + }) +} + +fn constant_by_law_rulings(channels: List) -> List { + fold(channels, init: [], f: (acc, c) => match c { + PerStateFamily { channel: _ } => acc + ConstantByLaw { channel_name: _, ruling } => concat(acc, [ruling]) + }) +} + +fn family_census_defects(css: String, f: StateFamily, channels: List) -> List { + fold(f.members, init: [], f: (acc, m) => + concat(acc, fold(per_family_channels(channels: channels), init: [], f: (inner, c) => + concat(inner, map(cell_fault(css: css, f: f, m: m, c: c), fault => + FamilyCensusDefect { member_key: m.key, channel_prop: channel_prop_wire(c: c), fault: fault }))))) +} + +fn family_census_cell_count(f: StateFamily, channels: List) -> Int { + count(f.members) * count(per_family_channels(channels: channels)) +} + +fn family_census_clean(css: String, f: StateFamily, channels: List) -> Bool { + count(family_census_defects(css: css, f: f, channels: channels)) == 0 +} + +fn constant_rulings_all_located(channels: List) -> Bool { + all(constant_by_law_rulings(channels: channels), r => + r.module_path.length() > 0 && r.decl_name.length() > 0) +} diff --git a/dag/gunbc/roadmap_interaction.dag b/dag/gunbc/roadmap_interaction.dag index b5d94eb4778..e98c54dd878 100644 --- a/dag/gunbc/roadmap_interaction.dag +++ b/dag/gunbc/roadmap_interaction.dag @@ -1,6 +1,7 @@ module gunbc.roadmap_interaction import std.types { Int, String, Bool } +import std.decl_ref { DeclarationRef, WholeDeclaration } import gunbc.design.material { TimingToken, respond_fast, reveal_deliberate, settle_spring, MotionTrigger, OnHover, OnActive, OnStateClass, @@ -8,6 +9,12 @@ import gunbc.design.material { CssDecl, decl, rest_scale_per_mille, press_scale_per_mille, hold_scale_per_mille, } +import gunbc.design.state_response { + StateFamily, StateFamilyMember, StateBinding, RestBinding, StateClassBinding, + StateChannel, RestChannel, ResponseChannel, ChannelScope, PerStateFamily, ConstantByLaw, + StateVarRole, FillVar, InkVar, PressedVar, + family_response_rules, +} import gunbc.design.interaction { InteractionVerb, Approach, Dispatch, Await, Accept, Enter, Response, Brighten, Receipt, Lift, Depress, Settle, Turn, @@ -22,7 +29,8 @@ import gunbc.design.interaction { } import gunbc.design.scale { travel_lift, travel_press, scale_var } import gunbc.roadmap_component { - dispatch_class_requested, dispatch_class_ok, sound_toggle_class, + dispatch_class_requested, dispatch_class_ok, dispatch_class_refused, dispatch_class_loud, + sound_toggle_class, disclosure_mark_class, disclosure_open_class, } import gunbc.site.moodboard { @@ -135,36 +143,43 @@ fn dispatch_btn_brighten_rules(timing: TimingToken) -> List { }) } -data dispatch_receipt_family_note: String = "The press receipt realizes PER STATE FAMILY (round-5 fix): the first cut was one constant on the base selector — the invite press fill for every state the button can wear — so pressing an orange fault pill flashed deep green. A press deepens the material it presses: each state class's :active targets ITS band's derived pressed var, and the family-preservation witness in the tactile suite holds a RED against any cross-family press returning." - -fn dispatch_state_receipt_rule(state_class: String, band_key: String, timing: TimingToken) -> BuildRule { - ResponseRule { - subject: RawSelector { text: join([".dispatch-btn.", state_class], "") }, - shape: SelfResponse, - trigger: OnActive, - transition_props: "background-color", - timing: timing, - base_decls: [], - response_decls: [ decl(prop: Background, value: join(["var(--band-", band_key, "-pressed)"], "")) ], - } +data dispatch_state_family_note: String = "The dispatch instrument's state family — the SINGLE authority for the state-class-to-band mapping (composition slice 1). Before this roster the mapping was spelled at three sites: the style's four per-state paint StaticRules, the receipt rules' four hand call sites, and the base rule's inline invite vars — the same fact three times, which is exactly the fork the round-5 flash grew in. The roster order is the emission order (rest first), pinned by the byte oracle. A presentation state the button can wear that is missing from this roster has no selector anywhere downstream — the realize folds and the census both walk this list, so joining the family IS the single edit." + +data dispatch_state_family: StateFamily = StateFamily { + component_class: "dispatch-btn", + var_prefix: "band-", + members: [ + StateFamilyMember { binding: RestBinding, key: "invite" }, + StateFamilyMember { binding: StateClassBinding { class_name: dispatch_class_requested }, key: "in-progress" }, + StateFamilyMember { binding: StateClassBinding { class_name: dispatch_class_ok }, key: "ok" }, + StateFamilyMember { binding: StateClassBinding { class_name: dispatch_class_refused }, key: "fail" }, + StateFamilyMember { binding: StateClassBinding { class_name: dispatch_class_loud }, key: "loud" }, + ], } +data dispatch_sound_constant_ratchet_note: String = "RULING ROW (composition slice 1, the honesty boundary): the engage sound is constant across the state family — one tick spec whichever band the instrument wears — and that constancy is DECLARED here as a ratchet, never left in the undeclared middle and never silent. It is a ratchet, not a wall: the tick cannot route per band until SoundOccasion becomes its closed sum (gunbc.design.sound.sound_growth_trigger, the settle-tone / refusal-thud PR), at which point the sound channel flips to PerStateFamily in dispatch_stateful_channels and this row deletes. Until then the census counts the channel as a signed constant with this row as its ruling." + +data dispatch_stateful_channels_note: String = "The dispatch instrument's stateful-channel table (composition slice 1) — every channel through which state reaches the operator, each carrying its scope BY TYPE: PerStateFamily rows are realized by the state_response folds (rest paints into the base rule and the per-state blocks; the press response multiplied across the family), ConstantByLaw rows name the recorded ruling that signed their constancy — the approach ring's band-neutrality (dispatch_brighten_neutral_note, operator 2026-07-24) and the sound ratchet (dispatch_sound_constant_ratchet_note). A response row with neither scope is unwritable: the table's element type has no third arm. Channel order within the rest group is the base rule's authored decl order (ink, fill, border), pinned by the byte oracle." + +data dispatch_stateful_channels: List = [ + PerStateFamily { channel: RestChannel { prop: Color, role: InkVar } }, + PerStateFamily { channel: RestChannel { prop: Background, role: FillVar } }, + PerStateFamily { channel: RestChannel { prop: BorderColor, role: FillVar } }, + PerStateFamily { channel: ResponseChannel { trigger: OnActive, prop: Background, role: PressedVar, transition_props: "background-color" } }, + ConstantByLaw { + channel_name: "approach-emphasis", + ruling: DeclarationRef { module_path: "gunbc.roadmap_interaction", decl_name: "dispatch_brighten_neutral_note", field: WholeDeclaration }, + }, + ConstantByLaw { + channel_name: "engage-sound", + ruling: DeclarationRef { module_path: "gunbc.roadmap_interaction", decl_name: "dispatch_sound_constant_ratchet_note", field: WholeDeclaration }, + }, +] + +data dispatch_receipt_family_note: String = "The press receipt realizes PER STATE FAMILY (round-5 fix, re-derived through the state_response fold in composition slice 1): the first cut was one constant on the base selector — the invite press fill for every state the button can wear — so pressing an orange fault pill flashed deep green. A press deepens the material it presses: the press-fill channel is authored ONCE in dispatch_stateful_channels as a function of state, and family_response_rules multiplies it across the roster — each member's selector targets ITS band's derived pressed var, the rest member on the base selector. The four hand call sites this replaces were byte-identical under the fold (digest oracle), and the family-preservation witness in the tactile suite holds a RED against any cross-family press returning." + fn dispatch_btn_receipt_rules(timing: TimingToken) -> List { - [ - ResponseRule { - subject: ClassSelector { name: "dispatch-btn" }, - shape: SelfResponse, - trigger: OnActive, - transition_props: "background-color", - timing: timing, - base_decls: [], - response_decls: [ decl(prop: Background, value: "var(--band-invite-pressed)") ], - }, - dispatch_state_receipt_rule(state_class: dispatch_class_requested, band_key: "in-progress", timing: timing), - dispatch_state_receipt_rule(state_class: dispatch_class_ok, band_key: "ok", timing: timing), - dispatch_state_receipt_rule(state_class: dispatch_class_refused, band_key: "fail", timing: timing), - dispatch_state_receipt_rule(state_class: dispatch_class_loud, band_key: "loud", timing: timing), - ] + family_response_rules(f: dispatch_state_family, channels: dispatch_stateful_channels, timing: timing) } data detent_realization_note: String = "Every transform rule is READ from the detent table, never restated beside it. That is the whole reason the table exists as data: a position written once in a table and again in a rule builder is the same fact in two places, and the copy that drifts is always the one nobody is looking at. So a position response (Lift, Depress, Settle) resolves its state in the table and emits whatever the table says; if the state is missing the realization yields NO rules, which surfaces through the existing unrealized-behaviour census as a counted row rather than as a silently absent transition. diff --git a/dag/gunbc/roadmap_style.dag b/dag/gunbc/roadmap_style.dag index c9b6661c801..63d68aeffa9 100644 --- a/dag/gunbc/roadmap_style.dag +++ b/dag/gunbc/roadmap_style.dag @@ -18,10 +18,16 @@ import gunbc.design.theme { FigureRole, FocusRole, BoundaryRole, } import gunbc.site.moodboard { figure_rules } -import gunbc.roadmap_interaction { roadmap_behavior_rules, roadmap_behaviors } +import gunbc.roadmap_interaction { + roadmap_behavior_rules, roadmap_behaviors, + dispatch_state_family, dispatch_stateful_channels, +} +import gunbc.design.state_response { + StateVarRole, FillVar, InkVar, PressedVar, + state_var_name, state_rest_decl, state_paint_rules, +} import gunbc.roadmap_component { - dispatch_reserved_width, dispatch_class_requested, dispatch_class_ok, dispatch_class_refused, - dispatch_class_loud, theme_toggle_class, theme_toggle_reserved_width, + dispatch_reserved_width, theme_toggle_class, theme_toggle_reserved_width, sound_toggle_class, sound_toggle_reserved_width, disclosure_mark_class, swatch_toggle_class, swatch_toggle_reserved_width, } @@ -91,11 +97,13 @@ fn band_paints_for_theme(t: Theme) -> List { workspace_band_paints() } +data band_var_grammar_note: String = "The band var NAMES route through gunbc.design.state_response.state_var_name with the family's own prefix (composition slice 1): the prefix and role-suffix grammar was previously spelled here AND in every consumer that referenced a var — the census, the receipt values, the paint rules — so a rename would have been a hunt. Now the family row owns the prefix and the role enum owns the suffixes; this fold and every reference derive from the same two authorities." + fn band_var_decls(rows: List) -> List { fold(rows, init: [], f: (acc, r) => concat(acc, [ - decl(prop: CustomProperty { name: join(["band-", r.key], "") }, value: swatch_paint(sw: r.fill)), - decl(prop: CustomProperty { name: join(["band-", r.key, "-ink"], "") }, value: swatch_paint(sw: r.ink)), - decl(prop: CustomProperty { name: join(["band-", r.key, "-pressed"], "") }, value: band_pressed_paint(r: r)), + decl(prop: CustomProperty { name: state_var_name(prefix: dispatch_state_family.var_prefix, key: r.key, role: FillVar) }, value: swatch_paint(sw: r.fill)), + decl(prop: CustomProperty { name: state_var_name(prefix: dispatch_state_family.var_prefix, key: r.key, role: InkVar) }, value: swatch_paint(sw: r.ink)), + decl(prop: CustomProperty { name: state_var_name(prefix: dispatch_state_family.var_prefix, key: r.key, role: PressedVar) }, value: band_pressed_paint(r: r)), ])) } @@ -209,7 +217,42 @@ fn roadmap_rules() -> List { concat(figure_rules(), roadmap_behavior_rules(rows: roadmap_behaviors))) } +data dispatch_btn_base_decls_note: String = "The instrument's base rule, with its rest-state paint DERIVED (composition slice 1): the ink/fill/border decls splice in from the family roster's rest member via state_rest_decl at the exact positions the authored order demands, so the rest-to-invite binding is spelled only in dispatch_state_family — the base rule can no longer disagree with the roster it used to restate. Geometry decls stay authored here; they are the button's own facts, not state functions." + +fn dispatch_btn_base_decls() -> List { + concat([ + decl(prop: FontSize, value: scale_var(t: text_13)), + decl(prop: FontFamily, value: scale_var(t: font_sans)), + ], + concat(state_rest_decl(f: dispatch_state_family, prop: Color, role: InkVar), + concat(state_rest_decl(f: dispatch_state_family, prop: Background, role: FillVar), + concat([ + decl(prop: Border, value: scale_border(w: border_1, color_var: "var(--border)")), + decl(prop: BorderRadius, value: scale_var(t: radius_999)), + decl(prop: Padding, value: scale_pair(a: space_8, b: space_16)), + decl(prop: MarginLeft, value: scale_var(t: space_6)), + decl(prop: Cursor, value: "pointer"), + decl(prop: Display, value: "inline-flex"), + decl(prop: AlignItems, value: "center"), + decl(prop: JustifyContent, value: "center"), + decl(prop: MinHeight, value: scale_var(t: height_control)), + decl(prop: MinWidth, value: dispatch_reserved_width()), + decl(prop: TextAlign, value: "center"), + ], + concat(state_rest_decl(f: dispatch_state_family, prop: BorderColor, role: FillVar), [ + decl(prop: FontWeight, value: "600"), + ]))))) +} + +data instance_rules_splice_note: String = "roadmap_instance_rules composes head + the state-paint fold + tail at the exact position the four hand per-state rules occupied (composition slice 1): the per-state blocks now emit from state_paint_rules over dispatch_state_family x dispatch_stateful_channels — roster order, channel order — so the mapping lives once and a new family member paints itself. The split into head/tail is placement, not meaning: byte order is pinned by the digest oracle." + fn roadmap_instance_rules() -> List { + concat(roadmap_instance_rules_head(), + concat(state_paint_rules(f: dispatch_state_family, channels: dispatch_stateful_channels), + roadmap_instance_rules_tail())) +} + +fn roadmap_instance_rules_head() -> List { [ StaticRule { selector: RawSelector { text: "*" }, decls: [ decl(prop: BoxSizing, value: "border-box"), @@ -451,25 +494,7 @@ fn roadmap_instance_rules() -> List { StaticRule { selector: RawSelector { text: ".brick-gated .brick-state" }, decls: [ role_decl(prop: Color, r: TextDimRole), ] }, - StaticRule { selector: ClassSelector { name: "dispatch-btn" }, decls: [ - decl(prop: FontSize, value: scale_var(t: text_13)), - decl(prop: FontFamily, value: scale_var(t: font_sans)), - decl(prop: Color, value: "var(--band-invite-ink)"), - decl(prop: Background, value: "var(--band-invite)"), - decl(prop: Border, value: scale_border(w: border_1, color_var: "var(--border)")), - decl(prop: BorderRadius, value: scale_var(t: radius_999)), - decl(prop: Padding, value: scale_pair(a: space_8, b: space_16)), - decl(prop: MarginLeft, value: scale_var(t: space_6)), - decl(prop: Cursor, value: "pointer"), - decl(prop: Display, value: "inline-flex"), - decl(prop: AlignItems, value: "center"), - decl(prop: JustifyContent, value: "center"), - decl(prop: MinHeight, value: scale_var(t: height_control)), - decl(prop: MinWidth, value: dispatch_reserved_width()), - decl(prop: TextAlign, value: "center"), - decl(prop: BorderColor, value: "var(--band-invite)"), - decl(prop: FontWeight, value: "600"), - ] }, + StaticRule { selector: ClassSelector { name: "dispatch-btn" }, decls: dispatch_btn_base_decls() }, StaticRule { selector: ClassSelector { name: sound_toggle_class }, decls: [ decl(prop: FontSize, value: scale_var(t: text_10)), decl(prop: FontFamily, value: scale_var(t: font_mono)), @@ -558,26 +583,11 @@ fn roadmap_instance_rules() -> List { decl(prop: FontSize, value: scale_var(t: text_11)), decl(prop: MarginRight, value: scale_var(t: space_6)), ] }, - StaticRule { selector: RawSelector { text: join([".dispatch-btn.", dispatch_class_requested], "") }, decls: [ - decl(prop: Color, value: "var(--band-in-progress-ink)"), - decl(prop: Background, value: "var(--band-in-progress)"), - decl(prop: BorderColor, value: "var(--band-in-progress)"), - ] }, - StaticRule { selector: RawSelector { text: join([".dispatch-btn.", dispatch_class_ok], "") }, decls: [ - decl(prop: Color, value: "var(--band-ok-ink)"), - decl(prop: Background, value: "var(--band-ok)"), - decl(prop: BorderColor, value: "var(--band-ok)"), - ] }, - StaticRule { selector: RawSelector { text: join([".dispatch-btn.", dispatch_class_refused], "") }, decls: [ - decl(prop: Color, value: "var(--band-fail-ink)"), - decl(prop: Background, value: "var(--band-fail)"), - decl(prop: BorderColor, value: "var(--band-fail)"), - ] }, - StaticRule { selector: RawSelector { text: join([".dispatch-btn.", dispatch_class_loud], "") }, decls: [ - decl(prop: Color, value: "var(--band-loud-ink)"), - decl(prop: Background, value: "var(--band-loud)"), - decl(prop: BorderColor, value: "var(--band-loud)"), - ] }, + ] +} + +fn roadmap_instance_rules_tail() -> List { + [ StaticRule { selector: ClassSelector { name: swatch_toggle_class }, decls: [ decl(prop: FontSize, value: scale_var(t: text_10)), decl(prop: FontFamily, value: scale_var(t: font_mono)), diff --git a/dag/test/claim/state_response_totality_witness_test.dag b/dag/test/claim/state_response_totality_witness_test.dag new file mode 100644 index 00000000000..7e3e3766b70 --- /dev/null +++ b/dag/test/claim/state_response_totality_witness_test.dag @@ -0,0 +1,103 @@ +module test.claim.state_response_totality_witness + +import gunbc.roadmap_style { roadmap_css, band_root_css } +import gunbc.roadmap_interaction { + dispatch_state_family, dispatch_stateful_channels, dispatch_btn_receipt_rules, +} +import gunbc.design.state_response { + StateFamily, StateFamilyMember, StateBinding, RestBinding, StateClassBinding, + ChannelScope, PerStateFamily, ConstantByLaw, + FamilyCensusDefect, FamilyCensusFault, FamilyVarUndefined, ChannelValueMissing, CrossFamilyValue, + family_census_defects, family_census_cell_count, family_census_clean, + constant_by_law_rulings, constant_rulings_all_located, + state_paint_rules, family_response_rules, +} +import gunbc.design.material { serialize_rules, respond_fast } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +data state_response_totality_witness_note: String = "Composition slice 1's census consumer (the interaction-totality law): every (dispatch state x stateful channel) cell resolves in-family over the EMITTED stylesheet or sits under a declared ConstantByLaw ruling; cells counted, zero undeclared, cross-family and dropped-member REDs live below as planted fixtures. Shaped for later StandingIntent enrollment per gunbc.design.state_response.state_response_census_note; the registry is deliberately not built here." + +test fn state_response_family_census_clean_on_live_emission() -> Bool { + return family_census_clean(css: roadmap_css(), f: dispatch_state_family, channels: dispatch_stateful_channels) +} + +data census_count_note: String = "The counted product: 5 family members (rest + requested/ok/refused/loud) x 4 per-family channels (rest ink, rest fill, rest border, press fill) = 20 cells, plus 2 signed constants (approach emphasis, engage sound) each carrying a located ruling ref. A widening family or channel table moves these numbers — the assertion is the count being CONSCIOUS, not the count being eternal." + +test fn state_response_census_counts_hold() -> Bool { + return family_census_cell_count(f: dispatch_state_family, channels: dispatch_stateful_channels) == 20 + && count(constant_by_law_rulings(channels: dispatch_stateful_channels)) == 2 + && constant_rulings_all_located(channels: dispatch_stateful_channels) +} + +data cross_wired_family_note: String = "The planted cross-family fixture: the ok member's selector bound to the FAIL band key — the round-5 flash class, synthesized at the roster grain. Emitting this family through the same realize folds and running the census with the TRUE roster must locate the ok cells as CrossFamilyValue naming fail. The var roots come from the real band_root_css. The fixture is selector-scoped (no base rule in the synthetic emission), so the rest member's cells also count ChannelValueMissing there — the assertion targets the located cross-family defect, never a defect-count of one." + +data cross_wired_family: StateFamily = StateFamily { + component_class: "dispatch-btn", + var_prefix: "band-", + members: [ + StateFamilyMember { binding: RestBinding, key: "invite" }, + StateFamilyMember { binding: StateClassBinding { class_name: "dispatch-requested" }, key: "in-progress" }, + StateFamilyMember { binding: StateClassBinding { class_name: "dispatch-ok" }, key: "fail" }, + StateFamilyMember { binding: StateClassBinding { class_name: "dispatch-refused" }, key: "fail" }, + StateFamilyMember { binding: StateClassBinding { class_name: "dispatch-loud" }, key: "loud" }, + ], +} + +fn synthetic_emission(f: StateFamily) -> String { + join([ + band_root_css(), + serialize_rules(rules: state_paint_rules(f: f, channels: dispatch_stateful_channels)), + serialize_rules(rules: family_response_rules(f: f, channels: dispatch_stateful_channels, timing: respond_fast)), + ], "\n") +} + +fn defect_is_cross_family_ok_to_fail(d: FamilyCensusDefect) -> Bool { + d.member_key == "ok" && match d.fault { + CrossFamilyValue { foreign_key } => foreign_key == "fail" + FamilyVarUndefined { var_name: _ } => false + ChannelValueMissing => false + } +} + +test fn state_response_census_reds_on_cross_family_press() -> Bool { + let poisoned = synthetic_emission(f: cross_wired_family) + let defects = family_census_defects(css: poisoned, f: dispatch_state_family, channels: dispatch_stateful_channels) + return count(defects) > 0 + && count(defects |> filter(d => defect_is_cross_family_ok_to_fail(d: d))) > 0 +} + +data dropped_member_family_note: String = "The planted dropped-member fixture: the loud member absent from the roster the emission was realized from. The census with the TRUE roster must count every loud cell ChannelValueMissing — the totality question the round-5 defect never got asked (one pressed var presenting as total), now a standing red. Same selector-scoped caveat as the cross-wired fixture: rest cells also count Missing in the synthetic; the assertion targets the located loud defect." + +data dropped_member_family: StateFamily = StateFamily { + component_class: "dispatch-btn", + var_prefix: "band-", + members: [ + StateFamilyMember { binding: RestBinding, key: "invite" }, + StateFamilyMember { binding: StateClassBinding { class_name: "dispatch-requested" }, key: "in-progress" }, + StateFamilyMember { binding: StateClassBinding { class_name: "dispatch-ok" }, key: "ok" }, + StateFamilyMember { binding: StateClassBinding { class_name: "dispatch-refused" }, key: "fail" }, + ], +} + +fn defect_is_loud_missing(d: FamilyCensusDefect) -> Bool { + d.member_key == "loud" && match d.fault { + ChannelValueMissing => true + CrossFamilyValue { foreign_key: _ } => false + FamilyVarUndefined { var_name: _ } => false + } +} + +test fn state_response_census_reds_on_dropped_member() -> Bool { + let partial = synthetic_emission(f: dropped_member_family) + let defects = family_census_defects(css: partial, f: dispatch_state_family, channels: dispatch_stateful_channels) + return count(defects) > 0 + && count(defects |> filter(d => defect_is_loud_missing(d: d))) > 0 +} + +data receipt_totality_note: String = "The fold-totality claim proven structurally, not by reading: the receipt realization must yield exactly one response rule per roster member — the shape whose absence let one pressed var present as total. This reds if the fold ever gains a filter or an arm that skips a member." + +test fn state_response_receipt_rules_cover_the_roster() -> Bool { + return count(dispatch_btn_receipt_rules(timing: respond_fast)) == count(dispatch_state_family.members) +} From 2cd30af3f4fca3703a5ed3f6748a8387a643506d Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 25 Jul 2026 13:58:35 -0400 Subject: [PATCH 2/7] Record the fourth-round rulings and the depth/motion reference set Session decisions 2026-07-25, recorded in the remodel plan (carrier rows deliberately deferred to their first consumers, per the register's own no-consumerless-rows discipline): - The "active operation may breathe" amendment SIGNED at the WIDE scope: MotionTrigger gains a general Sustained variant in the motion/depth PR; the decorative-vs-operational distinction demotes from type to counted census, stated at signing. - W2-exemplars re-sequenced: after the RoadmapRow archetype, own dispatch ahead of W2-bulk; the stale Sequencing line corrected (#7177 closed without them by the composition carve-out, not by omission). - The operator-supplied depth/motion references recorded with their concept anchors and port constraints (proxemic glow with the enveloped-breath mechanism; SUPERHOT cue decomposition with barrel excluded; Discord elevation as ElevationPlane shape; train-window parallax grounded on the layer DAG's real churn gradient) plus the sandbox demo ladder. The DesignProvenance carrier lands with the depth-studies PR. Doc-graph witnesses green (the plan doc is already a bound root). Co-Authored-By: Claude Fable 5 --- docs/plans/roadmap-workspace-remodel-plan.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/plans/roadmap-workspace-remodel-plan.md b/docs/plans/roadmap-workspace-remodel-plan.md index a7ef2c2d2ec..24bed6943c2 100644 --- a/docs/plans/roadmap-workspace-remodel-plan.md +++ b/docs/plans/roadmap-workspace-remodel-plan.md @@ -32,6 +32,16 @@ Third review round (2026-07-24, after W1c/W1e landed): - **Dwell 300 ms** (operator-tuned from 500). - **Motion-modeling session filed (W1h, follow-up PR):** multi-phase choreography — the Discord-mute shape: the state lands fast while the motion continues to hold attention (moving parts behind each button), i.e. phases with their own clocks + a sustained active-phase animation, not one dwell scalar. Requires the operator to sign an "active operation may breathe" amendment to still-until-touched. Also in that session: button size retune (40px reads a bit big), the overshoot call, and a principled derivation of the timing scale. +Fourth review round (2026-07-25, session decisions — recorded here; carrier rows land with their first consumers): +- **The "active operation may breathe" amendment is SIGNED, at the WIDE scope** (operator, 2026-07-25, choosing between a bounded `WhileOperating{state}` variant and general sustained motion): `MotionTrigger` gains a general `Sustained` variant — any element may carry declared sustained animation. Recorded consequence, stated at signing: the decorative-vs-operational distinction stops being decidable from the type, so the wall demotes to a counted census (a sustained row must be declared and counted; an undeclared one is the red) — construction where it remains (no trigger, no motion), census for what the wide scope opened. The variant, its census, and the ruling row land in the motion/depth PR with their first consumer; StillUntilTouched's text amends in that same PR, never before the carrier exists. +- **W2-exemplars ride AFTER the RoadmapRow archetype** (operator, 2026-07-25): their own dispatch ahead of W2-bulk, judged against finished composition rather than the layout the operator already called clunky. The Sequencing line below is corrected accordingly — the original "W2-exemplars ride this branch" was written before the composition session was carved out, and #7177 closed without them by that later carve, not by omission. +- **Depth & motion references (operator-supplied, 2026-07-25)** — the anchor set for the depth-studies PR that follows composition; the `DesignProvenance` carrier (concept · source · claim · register carrier, the extdeps move applied to design vocabulary) lands there with these as its first rows: + - *Proxemic glow* — `briansrls/dynamic-tierlist` login button (`App.tsx:296–347`): pursuit easing (lerp 0.001/frame), proximity brightness (distance [5,50] → brightness [3.5,0.5], inverse), breath (sin, 3 s period, ±15%) — and the load-bearing line `finalBrightness = distanceBrightness * pulseFactor`: the breath's amplitude is enveloped by proximity, so it is quiet at arm's length and alive up close — the register thesis implemented. Anchors: proxemics (Hall 1966) → proxemic interaction (Greenberg et al. 2011); an LFO through an envelope, the synthesis vocabulary `design/sound.dag` already models. Port constraints, found at review: `box-shadow` is repaint (not compositor); the blurple's sat 86 exceeds every area ceiling except Point/DataViz; per-frame React state → CSS custom property instead; `prefers-reduced-motion` collapse mandatory. + - *SUPERHOT piOS menu* (operator screenshot, 2026-07-25): diegetic interface (the game-UX taxonomy traced to Fagerholt & Lorentzon's "Beyond the HUD", 2009). Cue decomposition, ranked by load-bearing weight: bloom/halation on strokes ≫ full-bleed selection band ≫ vignette ≫ barrel curvature. First three are in-register primitives; barrel is excluded (needs displacement/WebGL — outside every lens; the register's CSS stays censusable). + - *Discord control silhouettes* (operator screenshot, 2026-07-25): a deliberate elevation system — contact-shadow/AO edge under a single implied light source; on dark surfaces elevation reads as rim light, not shadow (Material's dark-theme "higher = lighter"). Anchors: multiplane discrete planes (Disney multiplane camera, *The Old Mill* 1937; pop-up book), Material elevation. Register shape: `ElevationPlane` closed enum on `Material`, shadow/rim projected per theme; grounds `travel_lift`/`travel_press` (today the entire Z axis, two bare ±1px literals) as plane transitions. + - *The train window* (operator, 2026-07-25: layers tracking at different rates): motion parallax (Helmholtz; Gibson's optic flow) — the multiplane camera's differential tracking. The DerivedTruth grounding that makes it this repo's fact rather than decoration: dag's layer DAG (`std ← extdeps ← compiler ← workflow`) is a real z-order with a real churn gradient — sky = std (deep, slow), trees = workflow (near, fast) — so parallax ratios derive from import-DAG depth (later: measured churn), never hand-tuned. Peel interaction: exploded view + rack focus + Furnas DOI; aerial perspective (sat/contrast as f(z)) composes with the existing `area_max_sat` machinery. Guardrail regardless of study outcome: the window moves only with the pointer — peripheral vision is motion-biased, so idle drift beside a reading surface is the irritation variant. + - Demo ladder (sandbox gallery, post-composition, cost order): elevation study (static, cheapest, grounds existing literals) → multiplane constellation window (borrowed scenery / shakkei; `layer_stars` is scenery that satisfies DerivedTruth) → peel study → proxemic glow (rides the signed Sustained amendment). + W0 census disposition (close-out): rows 1–6 LANDED this branch (band fold W1a · state material W1d · text-channel law W1d · geometry axis W1c · disclosure + sound-toggle as register elements W1e, Component-type gallery enrollment rides W1f · caption/state fusion W1d via sublabels). Row 7 (link projection) rides W2. Row 8 (asset path) stays W3. Row 9 stays counted where it is. New residue found during the work: loam displaced from button inks (still base-set text capability, unaffected); brass displaced from dispatch approach (still the review mark's material). ## Composition physics (round 5 — the next grain; management-lane synthesis, confirmed and adopted) @@ -75,4 +85,4 @@ Served asset URL becomes a projection of node identity (qualified name or conten ## Sequencing -W0 + W1 + W2-exemplars ride this branch. W2-bulk and W3 are follow-on PRs. PR-B (sessions panel) pairs naturally with the ticket fields once they exist. +Corrected 2026-07-25 (operator ruling, fourth review round): W0 + W1 rode #7177. W2-exemplars ride their own dispatch AFTER the composition session lands the RoadmapRow archetype — judged against finished composition, ahead of W2-bulk (the original "W2-exemplars ride this branch" predates the composition carve-out; #7177 closed without them by that carve, not by omission). W2-bulk and W3 are follow-on PRs. The depth-studies PR (fourth-round references above) queues behind composition. PR-B (sessions panel) pairs naturally with the ticket fields once they exist. From c49db3f36548bdc4bc66bbbd2d2addd430c4e594 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 25 Jul 2026 14:11:10 -0400 Subject: [PATCH 3/7] =?UTF-8?q?Slice=202:=20the=20RoadmapRow=20archetype?= =?UTF-8?q?=20=E2=80=94=20declared=20grid,=20chip-scale=20actuator=20deriv?= =?UTF-8?q?ed=20from=20row=20scale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row is the component (composition slice 2; refines the round-5 flex head line, does not rebuild it). ARCHETYPE — gunbc.roadmap_component.roadmap_row_archetype: the five regions declared as data (status chip | title | meta | disclosure | actuator), each with a typed placement — chip and actuator own auto head tracks (identity left, action right), title owns the flex track, meta flows IN the title's cell (badge count varies, so meta is a flow, never a ragged column), disclosure sits under the head on its landed indent. The grid template is DERIVED from the placements (roadmap_row_grid_template = "auto 1fr auto" by fold, no magic string); density rides two tokens on the scale grammar (h-row, the row gutter) — no bare pixels. ACTUATOR — chip-scale in rows per the SIGNED ruling (2026-07-24): the row-scope override derives its extent from the ROW's token (min-height var(--h-row)) and its width ceiling from the same caption-derived reservation the base declares — min-width and max-width one authority, so a caption morph can never reflow the row. The full-size specimen keeps h-control BY SELECTOR SCOPE: the override applies only under .node-head, so the sandbox gallery renders the design-surface grain with the SAME press physics (detent + receipt folds target .dispatch-btn at both scales, per-family-honest — slice 1's census green over the new emission). DOM — node-mid wrapper (the title track's cell) in node_head_row; the margin-left:auto pusher rule deletes (the grid's third track places the actuator). CSS grain grows three cited property rows (grid-template-columns, text-overflow, white-space). DIGEST — re-pinned 8d2372be4c9d6d61, derived by execution; slice 1 was proven byte-identical against the previous pin first, so the two changes are separately attested. WITNESSES (new file, all by execution): the archetype-derived head block + node-mid block verbatim in the emitted CSS; the served page carries the cells; actuator derived-not-set (positive + planted RED on the hand-retune shape); density cells tokened. Full battery green: 8 keystones + slice-1 census + css grain. PROVISIONAL rows (iteration protocol, one-liner flips at the pass): h-row = 24px; overflow guard clips without an ellipsis glyph (real ellipsis needs a caption span, which moves the client textContent target — named, not smuggled); row gutter stays the landed space-4. Co-Authored-By: Claude Fable 5 --- dag/extdeps/languages/css/properties.dag | 6 ++ dag/gunbc/design/scale.dag | 6 +- dag/gunbc/roadmap_component.dag | 66 +++++++++++++ dag/gunbc/roadmap_page.dag | 23 +++-- dag/gunbc/roadmap_style.dag | 23 ++++- ...sign_register_lift_parity_witness_test.dag | 4 +- dag/test/claim/roadmap_row_witness_test.dag | 96 +++++++++++++++++++ 7 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 dag/test/claim/roadmap_row_witness_test.dag diff --git a/dag/extdeps/languages/css/properties.dag b/dag/extdeps/languages/css/properties.dag index 4928d3968f9..342c7e28a09 100644 --- a/dag/extdeps/languages/css/properties.dag +++ b/dag/extdeps/languages/css/properties.dag @@ -38,6 +38,7 @@ type CssPropertyName | FontVariantNumeric | FontWeight | Gap + | GridTemplateColumns | Height | JustifyContent | LetterSpacing @@ -58,10 +59,12 @@ type CssPropertyName | Stroke | TextAlign | TextDecoration + | TextOverflow | TextTransform | Transform | Transition | VerticalAlign + | WhiteSpace | Width | CustomProperty { name: String } | RawProperty { name: String } @@ -92,6 +95,7 @@ fn css_property_wire(p: CssPropertyName) -> String { FontVariantNumeric => "font-variant-numeric" FontWeight => "font-weight" Gap => "gap" + GridTemplateColumns => "grid-template-columns" Height => "height" JustifyContent => "justify-content" LetterSpacing => "letter-spacing" @@ -112,10 +116,12 @@ fn css_property_wire(p: CssPropertyName) -> String { Stroke => "stroke" TextAlign => "text-align" TextDecoration => "text-decoration" + TextOverflow => "text-overflow" TextTransform => "text-transform" Transform => "transform" Transition => "transition" VerticalAlign => "vertical-align" + WhiteSpace => "white-space" Width => "width" CustomProperty { name: n } => join(["--", n], "") RawProperty { name: n } => n diff --git a/dag/gunbc/design/scale.dag b/dag/gunbc/design/scale.dag index be2609168de..4ec3ee5e082 100644 --- a/dag/gunbc/design/scale.dag +++ b/dag/gunbc/design/scale.dag @@ -114,6 +114,10 @@ data height_bar: ScaleToken = named_length_token(name: "h-bar", px: 10) data height_control_note: String = "The tactile target height (remodel W1c, operator archetype 2026-07-24: the GitHub merge button — a control you press with confidence needs the hand-scale target, ~40px class, not a text-line strip). This token plus radius-999 (the oblong/pill radius) are the first two rows of the control-geometry axis; the axis stays token-grain until a SECOND instrument lands (the archetype library's own second-consumer discipline), at which point the rows lift into a typed ControlGeometry carrier." data height_control: ScaleToken = named_length_token(name: "h-control", px: 40) + +data height_row_note: String = "The roadmap row's density token (composition slice 2, the signed actuator ruling 2026-07-24: chip-scale in rows, sixty pills must be quiet at arm's length — the instrument feel is carried by press physics, never by area). The ROW owns this height and the in-row actuator DERIVES its extent from it (min-height and line-height both var(--h-row) — derived-not-set, witnessed): there is no separate chip-height token to drift against the row that contains it. h-control stays the FULL-SIZE instrument's height, consumed by the sandbox gallery specimen where design work lives. PROVISIONAL (iteration protocol): 24px is the worker's call pending the operator's pass — flip is this one row." + +data height_row: ScaleToken = named_length_token(name: "h-row", px: 24) data width_status: ScaleToken = named_length_token(name: "w-status", px: 52) data width_brick_state: ScaleToken = named_length_token(name: "w-brick-state", px: 62) @@ -124,7 +128,7 @@ data travel_press: ScaleToken = named_length_token(name: "travel-press", px: 1) data dimension_scale: List = [ radius_3, radius_5, radius_999, border_1, border_2, measure_page, - height_mark, height_bar, height_control, width_status, width_brick_state, + height_mark, height_bar, height_control, height_row, width_status, width_brick_state, travel_lift, travel_press, ] diff --git a/dag/gunbc/roadmap_component.dag b/dag/gunbc/roadmap_component.dag index ed35c823fa6..f261b2a82fc 100644 --- a/dag/gunbc/roadmap_component.dag +++ b/dag/gunbc/roadmap_component.dag @@ -2,6 +2,7 @@ module gunbc.roadmap_component import std.types { Int, String, Bool } import extdeps.languages.css.values { CssLength, css_ch, css_length_wire } +import gunbc.design.scale { ScaleToken, space_4, height_row } import gunbc.design.component { Component, ComponentState, ComponentStateKind, IdleState, RequestedState, TerminalState, @@ -134,6 +135,71 @@ data dispatch_class_ok: String = "dispatch-ok" data dispatch_class_refused: String = "dispatch-refused" data dispatch_class_loud: String = "dispatch-loud" +data roadmap_row_archetype_note: String = "The RoadmapRow archetype (composition slice 2; refines the round-5 flex head line, does not rebuild it): the row's five regions DECLARED as data — status chip, title, meta, disclosure, actuator — each with its placement, and the head line's grid template DERIVED from the placements rather than authored as a magic string. Placements: the chip and the actuator own auto head tracks (identity left, action right — the GitHub PR-row shape the operator named); the title owns the flex track; meta FLOWS inside the title's track (badge count varies per node, so meta is a flow, not a column); the disclosure sits under the head on its own line, indented by its landed token. Density rides two tokens on the scale grammar — roadmap_row_height (h-row; the actuator in rows derives its extent from THIS token, derived-not-set, per the signed chip-scale ruling) and roadmap_row_gutter — no bare pixels anywhere in the realization. The full-size instrument specimen (h-control grain) lives in the sandbox gallery by selector scope: the row override applies only under .node-head, so gallery buttons keep the design-surface geometry with the SAME press physics rules (the detent and receipt folds target .dispatch-btn in both contexts, per-family-honest everywhere)." + +type RowTrack + = AutoTrack + | FlexTrack + +fn row_track_wire(t: RowTrack) -> String { + match t { + AutoTrack => "auto" + FlexTrack => "1fr" + } +} + +type RowPlacement + = HeadTrack { track: RowTrack } + | InTitleFlow + | UnderHead + +type RowRegion + = StatusChipRegion + | TitleRegion + | MetaRegion + | DisclosureRegion + | ActuatorRegion + +fn row_region_key(r: RowRegion) -> String { + match r { + StatusChipRegion => "status-chip" + TitleRegion => "title" + MetaRegion => "meta" + DisclosureRegion => "disclosure" + ActuatorRegion => "actuator" + } +} + +type RoadmapRowRegion { + region: RowRegion + placement: RowPlacement +} + +data roadmap_row_archetype: List = [ + RoadmapRowRegion { region: StatusChipRegion, placement: HeadTrack { track: AutoTrack } }, + RoadmapRowRegion { region: TitleRegion, placement: HeadTrack { track: FlexTrack } }, + RoadmapRowRegion { region: MetaRegion, placement: InTitleFlow }, + RoadmapRowRegion { region: DisclosureRegion, placement: UnderHead }, + RoadmapRowRegion { region: ActuatorRegion, placement: HeadTrack { track: AutoTrack } }, +] + +data roadmap_row_height: ScaleToken = height_row +data roadmap_row_gutter: ScaleToken = space_4 + +fn roadmap_row_grid_template() -> String { + join( + fold(roadmap_row_archetype, init: [], f: (acc, r) => match r.placement { + HeadTrack { track } => concat(acc, [row_track_wire(t: track)]) + InTitleFlow => acc + UnderHead => acc + }), + " ") +} + +data node_mid_class_note: String = "The title track's cell: the wrapper that carries the title plus the flowing meta regions (badges, carrier links). It exists because the archetype's grid has exactly the head tracks as columns and a node's badge count varies — meta declared InTitleFlow realizes as flex flow inside this cell, never as a column that would ragged the grid." + +data node_mid_class: String = "node-mid" + data dispatch_caption_note: String = "The operator-facing captions (remodel W1d, operator ruling 2026-07-24): the wire taxonomy never renders as button text. The caption is the BAND's verb — ok keeps the session-chip morph (prefix + the belt's own status word, an honest projection, checkpoint 2); fail and loud collapse to one operator phrase each, with the wire label and its located fields demoted to the title/detail channel. BandInProgress renders the requested caption. These rows are the caption authority for the client program, the gallery, and the reserved width — one vocabulary, three consumers." data dispatch_caption_idle: String = "dispatch" diff --git a/dag/gunbc/roadmap_page.dag b/dag/gunbc/roadmap_page.dag index 5df356fd3eb..e66baf78fc7 100644 --- a/dag/gunbc/roadmap_page.dag +++ b/dag/gunbc/roadmap_page.dag @@ -25,7 +25,7 @@ import gunbc.roadmap_authority { roadmap_authority, authored_merged_prs } import gunbc.roadmap_spawner { spawn_frontier, SpawnFrontier, doc_all_nodes } import gunbc.roadmap_component { roadmap_dispatch_asset_path, sound_toggle_class, theme_toggle_class, - disclosure_mark_class, disclosure_mark_glyph, + disclosure_mark_class, disclosure_mark_glyph, node_mid_class, } import gunbc.v1_deletion_plan { v1_deletion_gates, v1_deletion_milestones, PlanGateSpec, PlanMilestone, @@ -514,21 +514,20 @@ fn page_script_census(page: MarkupNode) -> List { collect_script_violations(node: page, known_src: roadmap_dispatch_asset_path) } -data node_head_row_note: String = "The row composition (remodel round 5, operator: 'the positioning of everything is still very clunky' — the pill sat alone on its own line, the disclosure floated, badges ragged). One flex HEAD LINE per node — status chip, title, badges, carrier links, and the dispatch control pushed to the row's right edge (the GitHub PR-row shape: identity left, action right) — with the disclosure indented beneath it. This is the deferred P3 flex-container refactor landing with the operator as the browser: the earlier deferral reason ('unverifiable without a browser') dissolved when the review loop gained live eyes." +data node_head_row_note: String = "The row composition, on the declared archetype (composition slice 2; refines round 5's flex head line). The head line realizes the RoadmapRow archetype's head tracks: status chip in its auto track, the node-mid cell (title + the flowing meta regions — badges, carrier links) in the flex track, the actuator in its auto track at the right edge (the GitHub PR-row shape: identity left, action right). The grid template is DERIVED from the archetype's placements (roadmap_row_grid_template), the disclosure stays indented beneath on its landed token, and the wrapper exists because meta is declared InTitleFlow — badge count varies per node, so meta flows in the title's cell rather than owning a ragged column." fn node_head_row(rn: RoadmapNode, merged: List) -> MarkupNode { let status = node_status_symbol(rn: rn, merged: merged) el_class("div", "node-head", concat( - concat( - concat( - [ - el_class("span", concat("status status-", status), [ txt(status) ]), - el_class("span", "title", lead_nodes(line: rn.line)), - ], - concat(superseded_badge(rn: rn), owner_badge(rn: rn)), - ), - concat(sizing_badge(rn: rn), carrier_links(carriers: rn.carriers)), - ), + [ + el_class("span", concat("status status-", status), [ txt(status) ]), + el_class("span", node_mid_class, concat( + concat( + [ el_class("span", "title", lead_nodes(line: rn.line)) ], + concat(superseded_badge(rn: rn), owner_badge(rn: rn)), + ), + concat(sizing_badge(rn: rn), carrier_links(carriers: rn.carriers)))), + ], dispatch_button_nodes(rn: rn, merged: merged))) } diff --git a/dag/gunbc/roadmap_style.dag b/dag/gunbc/roadmap_style.dag index 63d68aeffa9..c031b4ff355 100644 --- a/dag/gunbc/roadmap_style.dag +++ b/dag/gunbc/roadmap_style.dag @@ -30,6 +30,7 @@ import gunbc.roadmap_component { dispatch_reserved_width, theme_toggle_class, theme_toggle_reserved_width, sound_toggle_class, sound_toggle_reserved_width, disclosure_mark_class, swatch_toggle_class, swatch_toggle_reserved_width, + roadmap_row_grid_template, roadmap_row_height, roadmap_row_gutter, node_mid_class, } import extdeps.color.srgb { Srgb8 } import extdeps.accessibility.wcag_contrast { srgb8_contrast_ratio_x100 } @@ -246,6 +247,8 @@ fn dispatch_btn_base_decls() -> List { data instance_rules_splice_note: String = "roadmap_instance_rules composes head + the state-paint fold + tail at the exact position the four hand per-state rules occupied (composition slice 1): the per-state blocks now emit from state_paint_rules over dispatch_state_family x dispatch_stateful_channels — roster order, channel order — so the mapping lives once and a new family member paints itself. The split into head/tail is placement, not meaning: byte order is pinned by the digest oracle." +data row_actuator_scale_note: String = "The in-row actuator at chip scale (composition slice 2, SIGNED ruling 2026-07-24: chip-scale in rows; instrument feel carried by press physics; full-size specimen in the sandbox gallery). DERIVED-NOT-SET: the row override's extent is the ROW's density token (min-height var(--h-row)) and its width ceiling is the same caption-derived reservation the base declares (min-width and max-width one authority, so the chip is fixed-width in rows and a caption morph can never reflow the row) — no fresh literal enters here, witnessed. The base .dispatch-btn keeps the h-control full-size grain, which is exactly what the sandbox gallery renders (row scope is .node-head descendant, gallery buttons sit outside it). Press physics untouched: the detent and receipt folds target .dispatch-btn and apply at both scales, per-family-honest. PROVISIONAL rows (iteration protocol, flips are one-liners): h-row = 24px; the overflow guard clips without an ellipsis glyph — real text-overflow needs a caption span inside the button, which moves the client program's textContent target, named for the pass rather than smuggled in; the gutter stays the landed space-4." + fn roadmap_instance_rules() -> List { concat(roadmap_instance_rules_head(), concat(state_paint_rules(f: dispatch_state_family, channels: dispatch_stateful_channels), @@ -511,13 +514,27 @@ fn roadmap_instance_rules_head() -> List { decl(prop: LetterSpacing, value: "0.03em"), ] }, StaticRule { selector: ClassSelector { name: "node-head" }, decls: [ + decl(prop: Display, value: "grid"), + decl(prop: GridTemplateColumns, value: roadmap_row_grid_template()), + decl(prop: Gap, value: scale_var(t: roadmap_row_gutter)), + decl(prop: AlignItems, value: "center"), + decl(prop: MinHeight, value: scale_var(t: roadmap_row_height)), + ] }, + StaticRule { selector: ClassSelector { name: node_mid_class }, decls: [ decl(prop: Display, value: "flex"), decl(prop: AlignItems, value: "center"), - decl(prop: Gap, value: scale_var(t: space_4)), + decl(prop: Gap, value: scale_var(t: roadmap_row_gutter)), decl(prop: FlexWrap, value: "wrap"), + decl(prop: MinWidth, value: scale_var(t: space_0)), ] }, - StaticRule { selector: RawSelector { text: ".node-head > .dispatch-btn" }, decls: [ - decl(prop: MarginLeft, value: "auto"), + StaticRule { selector: RawSelector { text: ".node-head .dispatch-btn" }, decls: [ + decl(prop: FontSize, value: scale_var(t: text_11)), + decl(prop: MinHeight, value: scale_var(t: roadmap_row_height)), + decl(prop: Padding, value: scale_pair(a: space_0, b: space_8)), + decl(prop: MarginLeft, value: scale_var(t: space_0)), + decl(prop: MaxWidth, value: dispatch_reserved_width()), + decl(prop: Overflow, value: "hidden"), + decl(prop: WhiteSpace, value: "nowrap"), ] }, StaticRule { selector: RawSelector { text: "details.item-detail" }, decls: [ decl(prop: Margin, value: scale_triple(a: space_1, b: space_0, c: space_3)), diff --git a/dag/test/claim/design_register_lift_parity_witness_test.dag b/dag/test/claim/design_register_lift_parity_witness_test.dag index aaae67b5756..ecd95ed1453 100644 --- a/dag/test/claim/design_register_lift_parity_witness_test.dag +++ b/dag/test/claim/design_register_lift_parity_witness_test.dag @@ -28,7 +28,9 @@ data roadmap_css_repin_remodel_w1_note: String = "Re-pinned 2026-07-24 (remodel data roadmap_css_repin_merge_note: String = "Merge resolution 2026-07-25 (main into the remodel branch). Both parents re-pinned this row off the same base value 947686b5dbaab2b0 for disjoint reasons: main via #7193 (namespace-flip emission drift, reaching roadmap_css through std.markup rather than through any design/ or roadmap_*/ file — none of those changed on main since the branch point), the branch via remodel W1 (band var family, dispatch rules re-painted from role borders to band fill/ink). The merged value is DERIVED, never chosen: roadmap_css() was emitted from the merged worktree and its content_hash_atom read off. Three-point control, all run against the same binary: origin/main emits d788728c4745ba48 (reproduces main's pin), the pre-merge branch tip d1d2fd954 emits 50410951147178f6 (reproduces the branch's pin), the merged tree emits 50410951147178f6. The merged value coinciding with the branch's is a real result, not a dropped merge: main's std.markup drift reaches an emission region the branch's W1 rewrite replaces, so the branch's bytes supersede it — and merge faithfulness is a separate receipt (of the 327 paths main touched since the base, 324 are byte-identical to main in the merged tree and 3 are the co-edited files, zero unexplained). Resolving this conflict by keeping either side's literal would have pinned bytes no tree emits." -data roadmap_css_lift_parity_digest: String = "50410951147178f6" +data roadmap_css_repin_row_archetype_note: String = "Re-pinned for composition slice 2 (the RoadmapRow archetype): the roadmap bytes move intentionally — the node-head flex rules become the archetype-derived grid (template from roadmap_row_grid_template, density on h-row and the row gutter), the node-mid cell rule and the chip-scale row actuator override are new emission, the margin-left:auto pusher rule deletes (the grid's third track places the actuator), and scale_root_css gains --h-row. Slice 1's re-derivation was proven byte-identical against the PREVIOUS pin (50410951147178f6, zero re-pin) before this slice moved bytes, so the two changes are separately attested. Behavioral receipts: the row archetype witness (grid-derived template + derived-not-set actuator + planted RED), roadmap_register_keystone, dispatch_presentation_keystone, roadmap_tactile_keystone (press physics unchanged at both scales). This digest only pins bytes; derived by execution from the merged tree." + +data roadmap_css_lift_parity_digest: String = "8d2372be4c9d6d61" data moodboard_thesis_themes_lift_parity_digest: String = "69e68066c49667f9" fn emission_content_digest(s: String) -> String { diff --git a/dag/test/claim/roadmap_row_witness_test.dag b/dag/test/claim/roadmap_row_witness_test.dag new file mode 100644 index 00000000000..3f29f83f697 --- /dev/null +++ b/dag/test/claim/roadmap_row_witness_test.dag @@ -0,0 +1,96 @@ +module test.claim.roadmap_row_witness + +import gunbc.roadmap_style { roadmap_css } +import gunbc.roadmap_page { roadmap_page_for_authority } +import gunbc.roadmap_component { + roadmap_row_archetype, roadmap_row_grid_template, + RoadmapRowRegion, RowPlacement, HeadTrack, InTitleFlow, UnderHead, + node_mid_class, dispatch_reserved_width, +} +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +data roadmap_row_witness_note: String = "Composition slice 2's receipts: the RoadmapRow archetype's grid asserted over the SERVED surfaces (the emitted stylesheet and the rendered page), the actuator's scale proven derived-not-set, and the density consumption pinned — the expected blocks are assembled from the archetype's own derivation fns and asserted verbatim against the emission, the same full-block pattern the page witness set. Declared bound: contains-grain cannot count occurrences, so a ROGUE duplicate block for the same selector is outside this file's reach — the register and tactile suites scan the wider sheet." + +fn expected_node_head_block() -> String { + join([ + ".node-head { display: grid; grid-template-columns: ", + roadmap_row_grid_template(), + "; gap: var(--space-4); align-items: center; min-height: var(--h-row) }", + ], "") +} + +fn expected_node_mid_block() -> String { + join([ + ".", node_mid_class, + " { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; min-width: var(--space-0) }", + ], "") +} + +fn expected_row_actuator_block() -> String { + join([ + ".node-head .dispatch-btn { font-size: var(--text-11); min-height: var(--h-row); padding: var(--space-0) var(--space-8); margin-left: var(--space-0); max-width: ", + dispatch_reserved_width(), + "; overflow: hidden; white-space: nowrap }", + ], "") +} + +data grid_structure_note: String = "The template the stylesheet carries must be the one DERIVED from the archetype's placements — the expected block embeds roadmap_row_grid_template(), so an archetype edit the stylesheet misses, or a hand template the archetype never declared, reds. The head-track count (3: chip auto, title 1fr, actuator auto) derives from the placement fold, never a literal claim about the string." + +fn head_track_count() -> Int { + count(roadmap_row_archetype |> filter(r => match r.placement { + HeadTrack { track: _ } => true + InTitleFlow => false + UnderHead => false + })) +} + +test fn roadmap_row_grid_structure_holds() -> Bool { + return string_contains(s: roadmap_css(), pattern: expected_node_head_block()) + && string_contains(s: roadmap_css(), pattern: expected_node_mid_block()) + && head_track_count() == 3 + && roadmap_row_grid_template() == "auto 1fr auto" +} + +fn authority_page_html() -> String { + match try_serialize_html_source(node: roadmap_page_for_authority()) { + EmitOk { html: h } => h + EmitRejected { reason: _ } => "" + } +} + +test fn roadmap_row_page_carries_the_cells() -> Bool { + let h = authority_page_html() + return h.length() > 0 + && string_contains(s: h, pattern: join(["class=\"", node_mid_class, "\""], "")) + && string_contains(s: h, pattern: "class=\"node-head\"") +} + +data actuator_derived_note: String = "Derived-not-set, both directions on ONE predicate: the block that ships must be the derived one (verbatim presence), and the derived block itself must draw extent from the row token and width from the caption reservation while carrying no fresh literal (no px, no h-control). The planted RED feeds the same predicate the exact block a well-meaning hand retune would write — the discrimination lives in the predicate, not the fixture." + +fn actuator_block_derived(block: String) -> Bool { + block.length() > 0 + && string_contains(s: block, pattern: "min-height: var(--h-row)") + && string_contains(s: block, pattern: "max-width: ") + && !string_contains(s: block, pattern: "h-control") + && !string_contains(s: block, pattern: "px") +} + +test fn roadmap_row_actuator_scale_derived_not_set() -> Bool { + return string_contains(s: roadmap_css(), pattern: expected_row_actuator_block()) + && actuator_block_derived(block: expected_row_actuator_block()) +} + +test fn roadmap_row_actuator_red_on_hand_scale() -> Bool { + let hand_scaled = ".node-head .dispatch-btn { font-size: var(--text-11); min-height: var(--h-control); padding: 3px 8px }" + return !actuator_block_derived(block: hand_scaled) +} + +data density_census_note: String = "The density cells of this slice's own emission, pinned tokened: the three expected blocks carry only var() lengths (the reservation's ch value is a typed derivation, not a bare pixel). The module-wide untokened-length census walls the class; this locates the row's own blocks." + +test fn roadmap_row_density_all_tokened() -> Bool { + return !string_contains(s: expected_node_head_block(), pattern: "px") + && !string_contains(s: expected_node_mid_block(), pattern: "px") + && !string_contains(s: expected_row_actuator_block(), pattern: "px") +} From 3e0841ebf38feb74c842e0fb0ce761e7e7d29dca Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 25 Jul 2026 14:27:44 -0400 Subject: [PATCH 4/7] =?UTF-8?q?Slice=203:=20page=20altitude=20from=20the?= =?UTF-8?q?=20attention=20law=20=E2=80=94=20bands=20with=20counts,=20deriv?= =?UTF-8?q?ed=20fold/expand?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace becomes the THIRD renderer of the observation model's "routine collapses, anomaly expands" (composition slice 3). LAW — gunbc.roadmap_altitude, a local derivation in std.observation's exact shape (attention derived-never-chosen, density derived from attention, one projection): done/superseded -> routine (folds), open/review -> working (holds), fail/loud -> anomaly (expands), with FAIL-CLOSED arms — an unmodeled status or band key is an anomaly, never quietly routine. No signature accepts per-section or per-node curation input, so hand-curation is unwritable, not discouraged. DECLARED CONVERGENCE ROW: std.observation lives on #7216's unmerged branch (supersedes #7162, the brief's original number — verified, and the successor read before shaping this). Dissolve-on: #7216 merges — RowAttention re-grounds on AttentionLevel, RowDensity on PresentationDensity, this module shrinks to the workspace's subject mapping. RENDER — node-bearing sections and frontier buckets become BANDS: a details.section-fold whose summary carries the title plus derived counts ("2 open · 1 review · 12 done", zeros omitted, unknown counted loudly), open exactly when non-routine work is inside. Prose-only sections have no altitude axis and stay flat. CLIENT — the terminal apply flips the row's disclosure open when the band's density says expands; the emitted predicate (false || band === 'fail' || band === 'loud') is DERIVED by folding the wire-band rows through the law. Receipts by execution: 8 altitude witnesses green — derivation totality with fail-closed REDs, counts wire pinned, fold/open proven over RENDERED fixtures (the two fixtures differ by exactly one working member), the derived chain pinned in the served asset with a negative arm. Fixture lesson kept as its own witness: a done-but-UNSIGNED node renders review and review is WORKING — the attention law agreeing with the sign-off gate (the operator's pending gesture holds a section open). Full battery green including roadmap_emit (the committed ROADMAP.md untouched). Digest re-pinned 5cfdb3c06e603e38, derived by execution. Co-Authored-By: Claude Fable 5 --- dag/gunbc/roadmap_altitude.dag | 114 ++++++++++++++++ dag/gunbc/roadmap_component.dag | 43 ++++++ dag/gunbc/roadmap_page.dag | 46 ++++++- dag/gunbc/roadmap_style.dag | 24 ++++ ...sign_register_lift_parity_witness_test.dag | 4 +- .../claim/roadmap_altitude_witness_test.dag | 127 ++++++++++++++++++ 6 files changed, 352 insertions(+), 6 deletions(-) create mode 100644 dag/gunbc/roadmap_altitude.dag create mode 100644 dag/test/claim/roadmap_altitude_witness_test.dag diff --git a/dag/gunbc/roadmap_altitude.dag b/dag/gunbc/roadmap_altitude.dag new file mode 100644 index 00000000000..7d8b51c7030 --- /dev/null +++ b/dag/gunbc/roadmap_altitude.dag @@ -0,0 +1,114 @@ +module gunbc.roadmap_altitude + +import std.types { Int, String, Bool } + +data roadmap_altitude_note: String = "Page altitude from the attention law (composition slice 3): the workspace becomes the THIRD renderer of the observation model's 'routine collapses, anomaly expands' — sections render as bands with counts, anomaly rows auto-expand, routine folds — DERIVED from node status and band key, never hand-curated. No per-section flag and no per-node curation input exists in any signature here; the only inputs are the status and band vocabularies other authorities own, which is what makes hand-curation unwritable rather than merely discouraged." + +data altitude_convergence_note: String = "DECLARED CONVERGENCE ROW (the honest local stand-in): std.observation — AttentionLevel derived-never-chosen, PresentationDensity as the single authority with collapse/expand projected from it — lives on #7216's unmerged branch (supersedes #7162; the brief's original number), not on main, so this module carries the law as a LOCAL derivation in the same shape: attention derived from state, density derived from attention, renderers consume density. Dissolve-on: #7216 merges — RowAttention re-grounds on std.observation.AttentionLevel, RowDensity on PresentationDensity, and this module shrinks to the workspace's subject mapping (node status / band key -> observation event class), the workspace then being one more renderer of the one authority rather than the holder of a twin." + +type RowAttention + = RowRoutine + | RowWorking + | RowAnomaly + +data status_attention_note: String = "The node-status projection: done and superseded are finished work (routine — they fold); open and review are the working set (present, never expanded by the machine — expansion is the operator's gesture); an UNMODELED status is an anomaly by fail-closed arm — a status this law has never heard of must shout, never quietly fold (DESIGN section 5: a failure arm refuses, it does not widen into routine)." + +fn status_attention(status: String) -> RowAttention { + if status == "done" || status == "superseded" { + RowRoutine + } else if status == "open" || status == "review" { + RowWorking + } else { + RowAnomaly + } +} + +type RowDensity + = RowCollapses + | RowHolds + | RowExpands + +data attention_density_note: String = "Density is DERIVED from attention — one projection, mirroring the observation module's density-single-authority lesson (its earlier shape carried two hand tables plus a witness that they agreed; the witness was validation where construction was available). Routine collapses; working holds its place without expanding; anomaly expands. There is no fourth arm and no per-consumer override." + +fn attention_density(a: RowAttention) -> RowDensity { + match a { + RowRoutine => RowCollapses + RowWorking => RowHolds + RowAnomaly => RowExpands + } +} + +data band_attention_note: String = "The dispatch-band projection (the client grain of the same law): ok is the machine reporting routine success — it folds; invite and in-progress are the working set; fail and loud are anomalies — the answer the operator must see expands. An unmodeled band key is an anomaly by the same fail-closed arm as an unmodeled status." + +fn band_attention(band_key: String) -> RowAttention { + if band_key == "ok" { + RowRoutine + } else if band_key == "invite" || band_key == "in-progress" { + RowWorking + } else if band_key == "fail" || band_key == "loud" { + RowAnomaly + } else { + RowAnomaly + } +} + +fn band_expands(band_key: String) -> Bool { + match attention_density(a: band_attention(band_key: band_key)) { + RowCollapses => false + RowHolds => false + RowExpands => true + } +} + +type SectionCounts { + open_count: Int + review_count: Int + done_count: Int + superseded_count: Int + other_count: Int +} + +fn section_counts(statuses: List) -> SectionCounts { + fold(statuses, init: SectionCounts { open_count: 0, review_count: 0, done_count: 0, superseded_count: 0, other_count: 0 }, f: (acc, s) => + if s == "open" { + SectionCounts { open_count: acc.open_count + 1, review_count: acc.review_count, done_count: acc.done_count, superseded_count: acc.superseded_count, other_count: acc.other_count } + } else if s == "review" { + SectionCounts { open_count: acc.open_count, review_count: acc.review_count + 1, done_count: acc.done_count, superseded_count: acc.superseded_count, other_count: acc.other_count } + } else if s == "done" { + SectionCounts { open_count: acc.open_count, review_count: acc.review_count, done_count: acc.done_count + 1, superseded_count: acc.superseded_count, other_count: acc.other_count } + } else if s == "superseded" { + SectionCounts { open_count: acc.open_count, review_count: acc.review_count, done_count: acc.done_count, superseded_count: acc.superseded_count + 1, other_count: acc.other_count } + } else { + SectionCounts { open_count: acc.open_count, review_count: acc.review_count, done_count: acc.done_count, superseded_count: acc.superseded_count, other_count: acc.other_count + 1 } + }) +} + +data section_counts_wire_note: String = "The band's count text: zero classes are OMITTED (a quiet band never says '0 review'), the classes render in working-first order (open, review, done, superseded), and the fail-closed other-bucket renders loudly as 'unknown' — an unmodeled status is never silently uncounted. An all-zero section renders an empty string; the caller decides whether such a section carries a band at all." + +fn count_phrase(n: Int, label: String) -> List { + if n == 0 { + [] + } else { + [join([to_string(n), " ", label], "")] + } +} + +fn section_counts_wire(c: SectionCounts) -> String { + join( + concat( + concat(count_phrase(n: c.open_count, label: "open"), count_phrase(n: c.review_count, label: "review")), + concat( + concat(count_phrase(n: c.done_count, label: "done"), count_phrase(n: c.superseded_count, label: "superseded")), + count_phrase(n: c.other_count, label: "unknown"))), + " · ") +} + +data statuses_all_routine_note: String = "The section fold predicate: a section folds when every node in it is routine — one anomalous or working member holds the whole section open, which is the law's altitude claim (the band summarizes; the anomaly refuses to be summarized). An empty status list is vacuously routine, and the section emitters do not wrap node-free sections at all, so the vacuous arm never decides a rendered fold." + +fn statuses_all_routine(statuses: List) -> Bool { + all(statuses, s => match status_attention(status: s) { + RowRoutine => true + RowWorking => false + RowAnomaly => false + }) +} diff --git a/dag/gunbc/roadmap_component.dag b/dag/gunbc/roadmap_component.dag index f261b2a82fc..4b6f9d10fb7 100644 --- a/dag/gunbc/roadmap_component.dag +++ b/dag/gunbc/roadmap_component.dag @@ -3,6 +3,7 @@ module gunbc.roadmap_component import std.types { Int, String, Bool } import extdeps.languages.css.values { CssLength, css_ch, css_length_wire } import gunbc.design.scale { ScaleToken, space_4, height_row } +import gunbc.roadmap_altitude { band_expands } import gunbc.design.component { Component, ComponentState, ComponentStateKind, IdleState, RequestedState, TerminalState, @@ -536,6 +537,29 @@ fn dispatch_band_chain_expr() -> TsExpr { )) } +data dispatch_expand_chain_note: String = "The client grain of the attention law (composition slice 3): which terminal bands auto-expand the row's disclosure is DERIVED — the distinct band keys of the wire-band rows filtered through gunbc.roadmap_altitude.band_expands (anomaly expands; routine and working never do) — so the emitted predicate `(band === \"fail\") || (band === \"loud\")` moves when either authority moves and no band name is hand-chosen at the emit site. The fold seeds `false`, which serializes as a visible leading arm rather than a fabricated default." + +fn key_present(keys: List, key: String) -> Bool { + fold(keys, init: false, f: (acc, k) => acc || k == key) +} + +fn dispatch_expand_band_keys() -> List { + fold(belt_dispatch_label_band_rows(), init: [], f: (acc, row) => + if band_expands(band_key: row.band_key) && !key_present(keys: acc, key: row.band_key) { + concat(acc, [row.band_key]) + } else { + acc + }) +} + +fn dispatch_expand_chain_expr() -> TsExpr { + fold(dispatch_expand_band_keys(), init: ts_ident(name: "false"), f: (acc, k) => + ts_binop( + left: acc, + op: "||", + right: ts_binop(left: ts_ident(name: "band"), op: "===", right: ts_lit_str(value: k)))) +} + fn dispatch_terminal_apply_body() -> TsStmt { ts_block(stmts: [ ts_const(name: "s", value: ts_binop(left: js_j_status(), op: "||", right: ts_lit_str(value: ""))), @@ -598,6 +622,25 @@ fn dispatch_terminal_apply_body() -> TsStmt { alternate: ts_ident(name: "base"), ), )), + ts_const(name: "expandRow", value: dispatch_expand_chain_expr()), + ts_const(name: "rowEl", value: ts_call(callee: js_b_member(name: "closest"), args: [ts_lit_str(value: "li")])), + ts_const(name: "dt", value: ts_binop( + left: ts_ident(name: "rowEl"), + op: "&&", + right: ts_call( + callee: ts_member(object: ts_ident(name: "rowEl"), property: "querySelector"), + args: [ts_lit_str(value: "details.item-detail")]), + )), + ts_if( + cond: ts_binop(left: ts_ident(name: "expandRow"), op: "&&", right: ts_ident(name: "dt")), + then: ts_block(stmts: [ + ts_expr_stmt(expr: ts_binop( + left: ts_member(object: ts_ident(name: "dt"), property: "open"), + op: "=", + right: ts_ident(name: "true"))), + ]), + else_stmt: none, + ), ts_if( cond: ts_prefix(op: "!", expr: ts_ident(name: "ok")), then: ts_block(stmts: [ diff --git a/dag/gunbc/roadmap_page.dag b/dag/gunbc/roadmap_page.dag index e66baf78fc7..2b8a4dee929 100644 --- a/dag/gunbc/roadmap_page.dag +++ b/dag/gunbc/roadmap_page.dag @@ -21,6 +21,7 @@ import gunbc.roadmap_status { item_superseded_by, item_is_done, signoff_accepts, signoff_by, } import gunbc.roadmap_document { RoadmapDocument, RoadmapSection } +import gunbc.roadmap_altitude { section_counts, section_counts_wire, statuses_all_routine, SectionCounts } import gunbc.roadmap_authority { roadmap_authority, authored_merged_prs } import gunbc.roadmap_spawner { spawn_frontier, SpawnFrontier, doc_all_nodes } import gunbc.roadmap_component { @@ -585,9 +586,43 @@ fn element_markup(e: SectionElement, merged: List) -> List { } } +data altitude_section_note: String = "Page altitude from the attention law (composition slice 3; gunbc.roadmap_altitude carries the law, this page is its renderer — the workspace as the third renderer of 'routine collapses, anomaly expands'). A node-bearing section renders as a BAND: a details.section-fold whose summary carries the section's title and its derived counts, open exactly when the section holds non-routine work — statuses_all_routine folds the finished lanes, one working or anomalous member holds the band open, and the open attribute is DERIVED, never authored per section. A section without nodes has no altitude axis and renders flat, so prose leads never fold themselves away." + +fn altitude_section(cls: String, title_nodes: List, statuses: List, body: List) -> MarkupNode { + if count(statuses) == 0 { + el_class("section", cls, concat(title_nodes, body)) + } else { + let counts = el_class("span", "section-counts", [ txt(section_counts_wire(c: section_counts(statuses: statuses))) ]) + let band = el_class("summary", "section-band", concat(title_nodes, [ counts ])) + let fold_attrs = if statuses_all_routine(statuses: statuses) { + [ MarkupAttr { name: "class", value: "section-fold" } ] + } else { + [ MarkupAttr { name: "class", value: "section-fold" }, MarkupAttr { name: "open", value: "" } ] + } + el_class("section", cls, [ + ElementNode { tag: "details", attrs: fold_attrs, children: concat([ band ], body) }, + ]) + } +} + +fn group_statuses(g: SectionGroup, merged: List) -> List { + map(g.nodes, rn => node_status_symbol(rn: rn, merged: merged)) +} + +fn section_statuses(s: RoadmapSection, merged: List) -> List { + fold(s.elements, init: [], f: (acc, e) => match e { + Prose { prose: _ } => acc + Group { group } => concat(acc, group_statuses(g: group, merged: merged)) + }) +} + fn section_markup(s: RoadmapSection, merged: List) -> MarkupNode { let body = fold(s.elements, init: [], f: (acc, e) => concat(acc, element_markup(e: e, merged: merged))) - el_class("section", "roadmap-section", concat([ el("h2", [ txt(s.title) ]) ], body)) + altitude_section( + cls: "roadmap-section", + title_nodes: [ el("h2", [ txt(s.title) ]) ], + statuses: section_statuses(s: s, merged: merged), + body: body) } fn frontier_flat_node_li(rn: RoadmapNode, merged: List) -> MarkupNode { @@ -606,10 +641,11 @@ fn frontier_bucket_ul(items: List, merged: List) -> MarkupNode } fn frontier_bucket_markup(label: String, cls: String, items: List, merged: List) -> MarkupNode { - el_class("section", concat("frontier-bucket frontier-", cls), [ - el("h2", [ txt(label) ]), - frontier_bucket_ul(items: items, merged: merged), - ]) + altitude_section( + cls: concat("frontier-bucket frontier-", cls), + title_nodes: [ el("h2", [ txt(label) ]) ], + statuses: map(items, rn => node_status_symbol(rn: rn, merged: merged)), + body: [ frontier_bucket_ul(items: items, merged: merged) ]) } fn roadmap_frontier_markup(frontier: SpawnFrontier, merged: List) -> MarkupNode { diff --git a/dag/gunbc/roadmap_style.dag b/dag/gunbc/roadmap_style.dag index c031b4ff355..d1e3ff7c580 100644 --- a/dag/gunbc/roadmap_style.dag +++ b/dag/gunbc/roadmap_style.dag @@ -305,6 +305,30 @@ fn roadmap_instance_rules_head() -> List { StaticRule { selector: ClassSelector { name: "roadmap-section" }, decls: [ decl(prop: MarginBottom, value: scale_var(t: space_24)), ] }, + StaticRule { selector: RawSelector { text: "details.section-fold > summary" }, decls: [ + decl(prop: Cursor, value: "pointer"), + decl(prop: ListStyle, value: "none"), + ] }, + StaticRule { selector: RawSelector { text: "details.section-fold > summary::-webkit-details-marker" }, decls: [ + decl(prop: Display, value: "none"), + ] }, + StaticRule { selector: ClassSelector { name: "section-band" }, decls: [ + decl(prop: Display, value: "flex"), + decl(prop: AlignItems, value: "baseline"), + decl(prop: Gap, value: scale_var(t: space_8)), + decl(prop: Padding, value: scale_pair(a: space_4, b: space_8)), + role_decl(prop: Background, r: SurfaceRole), + decl(prop: BorderRadius, value: scale_var(t: radius_3)), + ] }, + StaticRule { selector: RawSelector { text: ".section-band h2" }, decls: [ + decl(prop: Margin, value: scale_var(t: space_0)), + ] }, + StaticRule { selector: ClassSelector { name: "section-counts" }, decls: [ + decl(prop: FontSize, value: scale_var(t: text_11)), + decl(prop: FontFamily, value: scale_var(t: font_mono)), + role_decl(prop: Color, r: TextDimRole), + decl(prop: FontVariantNumeric, value: "tabular-nums"), + ] }, StaticRule { selector: ElementSelector { tag: "ul" }, decls: [ decl(prop: ListStyle, value: "none"), decl(prop: PaddingLeft, value: scale_var(t: space_16)), diff --git a/dag/test/claim/design_register_lift_parity_witness_test.dag b/dag/test/claim/design_register_lift_parity_witness_test.dag index ecd95ed1453..f54fd4a1345 100644 --- a/dag/test/claim/design_register_lift_parity_witness_test.dag +++ b/dag/test/claim/design_register_lift_parity_witness_test.dag @@ -30,7 +30,9 @@ data roadmap_css_repin_merge_note: String = "Merge resolution 2026-07-25 (main i data roadmap_css_repin_row_archetype_note: String = "Re-pinned for composition slice 2 (the RoadmapRow archetype): the roadmap bytes move intentionally — the node-head flex rules become the archetype-derived grid (template from roadmap_row_grid_template, density on h-row and the row gutter), the node-mid cell rule and the chip-scale row actuator override are new emission, the margin-left:auto pusher rule deletes (the grid's third track places the actuator), and scale_root_css gains --h-row. Slice 1's re-derivation was proven byte-identical against the PREVIOUS pin (50410951147178f6, zero re-pin) before this slice moved bytes, so the two changes are separately attested. Behavioral receipts: the row archetype witness (grid-derived template + derived-not-set actuator + planted RED), roadmap_register_keystone, dispatch_presentation_keystone, roadmap_tactile_keystone (press physics unchanged at both scales). This digest only pins bytes; derived by execution from the merged tree." -data roadmap_css_lift_parity_digest: String = "8d2372be4c9d6d61" +data roadmap_css_repin_altitude_note: String = "Re-pinned for composition slice 3 (page altitude): new emission for the section band — the fold summary rules (marker suppressed, pointer), the band strip (surface fill, baseline flex, tokened gap/padding/radius), the in-band h2 margin reset, and the counts chip (mono, dim, tabular). Behavioral receipts: the altitude witness (derivation totality, fold/open by statuses, counts wire, client expand chain with planted REDs), roadmap_page_keystone. Derived by execution; this digest only pins bytes." + +data roadmap_css_lift_parity_digest: String = "5cfdb3c06e603e38" data moodboard_thesis_themes_lift_parity_digest: String = "69e68066c49667f9" fn emission_content_digest(s: String) -> String { diff --git a/dag/test/claim/roadmap_altitude_witness_test.dag b/dag/test/claim/roadmap_altitude_witness_test.dag new file mode 100644 index 00000000000..2a0f297c8b2 --- /dev/null +++ b/dag/test/claim/roadmap_altitude_witness_test.dag @@ -0,0 +1,127 @@ +module test.claim.roadmap_altitude_witness + +import gunbc.roadmap_altitude { + RowAttention, RowRoutine, RowWorking, RowAnomaly, + RowDensity, RowCollapses, RowHolds, RowExpands, + status_attention, attention_density, band_attention, band_expands, + SectionCounts, section_counts, section_counts_wire, statuses_all_routine, +} +import gunbc.roadmap_component { dispatch_expand_band_keys, dispatch_client_js_source } +import gunbc.roadmap_page { roadmap_page } +import gunbc.roadmap_model { RoadmapNode, section_group } +import gunbc.roadmap_document { RoadmapDocument, RoadmapSection } +import gunbc.roadmap_authority { authored, signed } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +data roadmap_altitude_witness_note: String = "Composition slice 3's receipts: the attention law's derivations proven total and fail-closed, the fold/open decision proven over RENDERED fixtures (a routine section folds, one working member holds it open), the counts wire pinned, and the client expand chain proven derived — each by execution, with the REDs planted as fixtures the same predicates must refuse." + +data derivation_totality_note: String = "The law's arms, pinned: finished work is routine, the working set holds, fail and loud expand — and the fail-closed arms are load-bearing, so they are asserted as REDs would be: an unmodeled status and an unmodeled band key must be anomalies, never quietly routine (the absorbing-fallback shape, refused)." + +fn attention_key(a: RowAttention) -> String { + match a { + RowRoutine => "routine" + RowWorking => "working" + RowAnomaly => "anomaly" + } +} + +fn density_key(d: RowDensity) -> String { + match d { + RowCollapses => "collapses" + RowHolds => "holds" + RowExpands => "expands" + } +} + +test fn altitude_status_derivation_holds() -> Bool { + return attention_key(a: status_attention(status: "done")) == "routine" + && attention_key(a: status_attention(status: "superseded")) == "routine" + && attention_key(a: status_attention(status: "open")) == "working" + && attention_key(a: status_attention(status: "review")) == "working" + && attention_key(a: status_attention(status: "zombie")) == "anomaly" +} + +test fn altitude_band_derivation_holds() -> Bool { + return band_expands(band_key: "fail") + && band_expands(band_key: "loud") + && !band_expands(band_key: "ok") + && !band_expands(band_key: "invite") + && !band_expands(band_key: "in-progress") + && band_expands(band_key: "never-heard-of-it") + && density_key(d: attention_density(a: RowRoutine)) == "collapses" + && density_key(d: attention_density(a: RowWorking)) == "holds" + && density_key(d: attention_density(a: RowAnomaly)) == "expands" +} + +test fn altitude_counts_wire_holds() -> Bool { + let c = section_counts(statuses: ["open", "open", "review", "done", "weird"]) + return section_counts_wire(c: c) == "2 open · 1 review · 1 done · 1 unknown" + && section_counts_wire(c: section_counts(statuses: ["done"])) == "1 done" + && statuses_all_routine(statuses: ["done", "superseded"]) + && !statuses_all_routine(statuses: ["done", "open"]) + && !statuses_all_routine(statuses: ["done", "zombie"]) +} + +fn doc_html(nodes: List) -> String { + let sec = RoadmapSection { title: "Belt", elements: [ section_group(label: "", nodes: nodes, edges: []) ] } + let doc = RoadmapDocument { preamble: [], lead_lanes: [], sections: [sec] } + match try_serialize_html_source(node: roadmap_page(doc: doc, merged: [])) { + EmitOk { html: h } => h + EmitRejected { reason: _ } => "" + } +} + +fn done_signed_node() -> RoadmapNode { + let base = authored(id: "alt-done", done: true, content: "finished work") + RoadmapNode { + node: base.node, + line: base.line, + carriers: base.carriers, + sizing: base.sizing, + owner: base.owner, + signoff: signed(by: "op", works: true, scope_equivalent: true, as_expected: true), + } +} + +fn open_node() -> RoadmapNode { + authored(id: "alt-open", done: false, content: "working item") +} + +data fold_by_statuses_note: String = "The fold decision proven over the RENDERED page, not the predicate alone: an all-routine section serializes its details WITHOUT the open attribute (folded — the summary band still carries the counts, so nothing is hidden silently), and adding one working member to the same section flips the SAME serialization to open. The two fixtures differ by exactly that member, so the discrimination is located in the status data — hand-curation has no input to reach. Fixture lesson, kept: a done-but-UNSIGNED node renders review (the sign-off gate), and review is WORKING under the law — the operator's pending gesture holds a section open, which is the attention law agreeing with the sign-off discipline rather than a coincidence. Routine therefore requires the signed fixture." + +test fn altitude_routine_section_folds() -> Bool { + let folded = doc_html(nodes: [done_signed_node()]) + return string_contains(s: folded, pattern: "
") + && !string_contains(s: folded, pattern: "
") + && string_contains(s: folded, pattern: "1 done") +} + +test fn altitude_working_member_holds_section_open() -> Bool { + let held = doc_html(nodes: [done_signed_node(), open_node()]) + return string_contains(s: held, pattern: "
") + && string_contains(s: held, pattern: "1 open · 1 done") +} + +test fn altitude_unsigned_done_holds_section_open() -> Bool { + let unsigned_done = doc_html(nodes: [authored(id: "alt-unsigned", done: true, content: "done but unsigned")]) + return string_contains(s: unsigned_done, pattern: "
") + && string_contains(s: unsigned_done, pattern: "1 review") +} + +data expand_chain_note: String = "The client chain proven derived, both directions: the derived key set is exactly the anomaly bands (order from the wire rows), the emitted asset carries the chain over those keys plus the disclosure flip, and the negative arm pins that no non-expanding band appears in an expand test — the exact defect a hand-authored chain would plant." + +test fn altitude_expand_keys_derive_from_the_law() -> Bool { + let keys = dispatch_expand_band_keys() + return count(keys) == 2 + && all(keys, k => band_expands(band_key: k)) +} + +test fn altitude_client_carries_the_derived_chain() -> Bool { + let js = dispatch_client_js_source() + return string_contains(s: js, pattern: "const expandRow = false || band === 'fail' || band === 'loud';") + && string_contains(s: js, pattern: "details.item-detail") + && string_contains(s: js, pattern: "dt.open = true") + && !string_contains(s: js, pattern: "band === 'ok' ||") +} From 0e869f509227c6b2c1c3df1a0262d8481958ada4 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 25 Jul 2026 14:33:38 -0400 Subject: [PATCH 5/7] =?UTF-8?q?Slice=204:=20anomaly=20evidence=20is=20a=20?= =?UTF-8?q?surface=20=E2=80=94=20the=20loud=20reason=20as=20the=20disclosu?= =?UTF-8?q?re's=20first=20line?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The loud band's located reason (step: detail, or the wire reason) renders as the first line of the row's disclosure — persistent, inspectable in the DOM — and the title attribute stays a convenience channel, never the only carrier (composition slice 4). CLIENT — in the terminal apply, gated on the loud band with the disclosure in hand: upsert .disclosure-reason (query, create-if-absent, insert after the summary so it IS the first line), textContent from the same computed `why` the title channel projects. Fail-closed: an empty wire reason writes "belt fault — no located reason on the wire" — a true statement, never an empty line that reads as fine. STYLE — one rule: text-12, theme ink, loud's own hue as a border-left edge mark, tokened lengths. Digest re-pinned 3a0411e16e69f2ab, derived by execution. WITNESSES — the surface predicate (upsert + insertion + why + fail-closed placeholder, loud-gated); the planted RED: a title-only fixture — the real title assignment's own shape with no disclosure machinery around it — must fail the same predicate; the title convenience channel asserted still present; the styled block pinned verbatim. Full battery green: 8 keystones + slice 1/2/3 witnesses + roadmap_emit + doc graph. Co-Authored-By: Claude Fable 5 --- dag/gunbc/roadmap_component.dag | 44 +++++++++++++++++++ dag/gunbc/roadmap_style.dag | 7 +++ .../claim/anomaly_evidence_witness_test.dag | 41 +++++++++++++++++ ...sign_register_lift_parity_witness_test.dag | 4 +- 4 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 dag/test/claim/anomaly_evidence_witness_test.dag diff --git a/dag/gunbc/roadmap_component.dag b/dag/gunbc/roadmap_component.dag index 4b6f9d10fb7..dd53d15eeb9 100644 --- a/dag/gunbc/roadmap_component.dag +++ b/dag/gunbc/roadmap_component.dag @@ -641,6 +641,50 @@ fn dispatch_terminal_apply_body() -> TsStmt { ]), else_stmt: none, ), + ts_if( + cond: ts_binop( + left: ts_binop(left: ts_ident(name: "band"), op: "===", right: ts_lit_str(value: "loud")), + op: "&&", + right: ts_ident(name: "dt")), + then: ts_block(stmts: [ + ts_let(name: "rl", value: ts_call( + callee: ts_member(object: ts_ident(name: "dt"), property: "querySelector"), + args: [ts_lit_str(value: ".disclosure-reason")])), + ts_if( + cond: ts_prefix(op: "!", expr: ts_ident(name: "rl")), + then: ts_block(stmts: [ + ts_expr_stmt(expr: ts_binop( + left: ts_ident(name: "rl"), + op: "=", + right: ts_call( + callee: ts_member(object: ts_ident(name: "document"), property: "createElement"), + args: [ts_lit_str(value: "div")]))), + ts_expr_stmt(expr: ts_binop( + left: ts_member(object: ts_ident(name: "rl"), property: "className"), + op: "=", + right: ts_lit_str(value: "disclosure-reason"))), + ts_const(name: "sm", value: ts_call( + callee: ts_member(object: ts_ident(name: "dt"), property: "querySelector"), + args: [ts_lit_str(value: "summary")])), + ts_expr_stmt(expr: ts_binop( + left: ts_ident(name: "sm"), + op: "&&", + right: ts_call( + callee: ts_member(object: ts_ident(name: "sm"), property: "insertAdjacentElement"), + args: [ts_lit_str(value: "afterend"), ts_ident(name: "rl")]))), + ]), + else_stmt: none, + ), + ts_expr_stmt(expr: ts_binop( + left: ts_member(object: ts_ident(name: "rl"), property: "textContent"), + op: "=", + right: ts_binop( + left: ts_ident(name: "why"), + op: "||", + right: ts_lit_str(value: "belt fault — no located reason on the wire")))), + ]), + else_stmt: none, + ), ts_if( cond: ts_prefix(op: "!", expr: ts_ident(name: "ok")), then: ts_block(stmts: [ diff --git a/dag/gunbc/roadmap_style.dag b/dag/gunbc/roadmap_style.dag index d1e3ff7c580..e8a79141c82 100644 --- a/dag/gunbc/roadmap_style.dag +++ b/dag/gunbc/roadmap_style.dag @@ -583,6 +583,13 @@ fn roadmap_instance_rules_head() -> List { role_decl(prop: Color, r: TextDimRole), decl(prop: MarginRight, value: scale_var(t: space_2)), ] }, + StaticRule { selector: ClassSelector { name: "disclosure-reason" }, decls: [ + decl(prop: FontSize, value: scale_var(t: text_12)), + role_decl(prop: Color, r: TextRole), + decl(prop: BorderLeft, value: scale_border(w: border_2, color_var: "var(--band-loud)")), + decl(prop: PaddingLeft, value: scale_var(t: space_6)), + decl(prop: Margin, value: scale_triple(a: space_2, b: space_0, c: space_2)), + ] }, StaticRule { selector: ClassSelector { name: "item-body" }, decls: [ decl(prop: FontSize, value: scale_var(t: text_12)), role_decl(prop: Color, r: TextRole), diff --git a/dag/test/claim/anomaly_evidence_witness_test.dag b/dag/test/claim/anomaly_evidence_witness_test.dag new file mode 100644 index 00000000000..cc4b1a8af1c --- /dev/null +++ b/dag/test/claim/anomaly_evidence_witness_test.dag @@ -0,0 +1,41 @@ +module test.claim.anomaly_evidence_witness + +import gunbc.roadmap_component { dispatch_client_js_source } +import gunbc.roadmap_style { roadmap_css } +import v2.std.live_tree { LiveTreeDisposition, SubstrateInputsOnly } + +data live_tree_disposition: LiveTreeDisposition = SubstrateInputsOnly + +data anomaly_evidence_witness_note: String = "Composition slice 4: anomaly evidence is a SURFACE, not a tooltip. The loud band's located reason (step: detail, or the wire reason) renders as the first line of the row's disclosure — persistent and inspectable in the DOM — while the title attribute stays a convenience channel, never the only carrier. The predicate below defines what 'is a surface' means mechanically; the RED feeds it the title-only shape (yesterday's program) and it must refuse." + +data reason_surface_predicate_note: String = "A client program carries the reason surface exactly when it: upserts the .disclosure-reason element (query, create-if-absent, insert after the summary so it is the disclosure's first line), writes the computed why into its textContent, and falls closed to a true statement when the wire carried no located reason — never an empty line that reads as fine. The insertion is gated on the loud band, so routine answers never grow evidence chrome." + +fn reason_is_a_disclosure_surface(js: String) -> Bool { + string_contains(s: js, pattern: "band === 'loud' && dt") + && string_contains(s: js, pattern: "dt.querySelector('.disclosure-reason')") + && string_contains(s: js, pattern: "document.createElement('div')") + && string_contains(s: js, pattern: "rl.className = 'disclosure-reason'") + && string_contains(s: js, pattern: "sm.insertAdjacentElement('afterend', rl)") + && string_contains(s: js, pattern: "rl.textContent = why || 'belt fault — no located reason on the wire'") +} + +test fn anomaly_reason_is_a_surface() -> Bool { + return reason_is_a_disclosure_surface(js: dispatch_client_js_source()) +} + +data title_only_red_note: String = "The planted RED: a loud fixture whose reason exists ONLY in the title channel — the exact fragment the live program also carries for the convenience channel, with no disclosure-reason machinery around it — must fail the surface predicate. The fixture is the real title assignment's shape, so the discrimination is the predicate refusing the tooltip-only world, not a strawman string." + +test fn anomaly_reason_red_on_title_only() -> Bool { + let title_only = "const why = j.step ? j.step + ': ' + d : j.reason || ''; b.title = why ? base + ' — ' + why : base;" + return !reason_is_a_disclosure_surface(js: title_only) +} + +test fn anomaly_title_stays_a_convenience() -> Bool { + let js = dispatch_client_js_source() + return string_contains(s: js, pattern: "b.title = why ? base +") + && string_contains(s: js, pattern: "' — '") +} + +test fn anomaly_reason_surface_is_styled() -> Bool { + return string_contains(s: roadmap_css(), pattern: ".disclosure-reason { font-size: var(--text-12); color: var(--text); border-left: var(--border-2) solid var(--band-loud); padding-left: var(--space-6); margin: var(--space-2) var(--space-0) var(--space-2) }") +} diff --git a/dag/test/claim/design_register_lift_parity_witness_test.dag b/dag/test/claim/design_register_lift_parity_witness_test.dag index f54fd4a1345..57af251be66 100644 --- a/dag/test/claim/design_register_lift_parity_witness_test.dag +++ b/dag/test/claim/design_register_lift_parity_witness_test.dag @@ -32,7 +32,9 @@ data roadmap_css_repin_row_archetype_note: String = "Re-pinned for composition s data roadmap_css_repin_altitude_note: String = "Re-pinned for composition slice 3 (page altitude): new emission for the section band — the fold summary rules (marker suppressed, pointer), the band strip (surface fill, baseline flex, tokened gap/padding/radius), the in-band h2 margin reset, and the counts chip (mono, dim, tabular). Behavioral receipts: the altitude witness (derivation totality, fold/open by statuses, counts wire, client expand chain with planted REDs), roadmap_page_keystone. Derived by execution; this digest only pins bytes." -data roadmap_css_lift_parity_digest: String = "5cfdb3c06e603e38" +data roadmap_css_repin_anomaly_evidence_note: String = "Re-pinned for composition slice 4 (anomaly evidence is a surface): one new rule — .disclosure-reason, the loud reason's persistent line in the row's disclosure (loud's own hue as the edge mark, tokened lengths, theme ink). Behavioral receipts: the anomaly-evidence witness (surface predicate + title-only RED + styled block pinned verbatim). Derived by execution; this digest only pins bytes." + +data roadmap_css_lift_parity_digest: String = "3a0411e16e69f2ab" data moodboard_thesis_themes_lift_parity_digest: String = "69e68066c49667f9" fn emission_content_digest(s: String) -> String { From c70eeaa9a7468ca2126fafbbfd904e5cdab3e23c Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 25 Jul 2026 14:34:58 -0400 Subject: [PATCH 6/7] Composition close-out: carrier rows flipped to landed All four slices LANDED on #7234, marked on the carrier per the done-line; PROVISIONAL rows listed in the PR body; the person-observable checkpoints await the operator's pass on the live page. Co-Authored-By: Claude Fable 5 --- docs/plans/roadmap-workspace-remodel-plan.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/plans/roadmap-workspace-remodel-plan.md b/docs/plans/roadmap-workspace-remodel-plan.md index 24bed6943c2..c25317e4279 100644 --- a/docs/plans/roadmap-workspace-remodel-plan.md +++ b/docs/plans/roadmap-workspace-remodel-plan.md @@ -54,6 +54,8 @@ The remodel applied the operator's laws at the ATOM grain (one button, one capti - **Grain 4 — anomaly evidence is a surface, not a tooltip.** The loud band's located reason (step/detail) renders as the first line of the row's disclosure — inspectable, persistent; the title attribute stays a convenience, never the only carrier. - **The interaction-totality law (operator question, round 5: how do we prevent the flash class?).** No new framework — one missing rule kind and one declared axis inside the existing register. The defect's mechanism: a response row's decls are CONSTANTS, so a state-dependent channel is either hand-multiplied across state selectors or accidentally constant-ized; the press fill was authored in the one channel that bypassed the detent-table pattern (transforms keyed by state as data, realization reads the table, gaps counted). Prevention, §5-ordered: (1) CONSTRUCTION — a StateResponse rule kind (the detent table generalized past transforms): author `press fill = state.band.pressed` once as a function of state, the realize fold multiplies it across the component's state family, a missing family unwritable by totality of the fold; (2) DECLARATION — ResponseScope = PerStateFamily | ConstantByLaw { ruling: DeclarationRef } on every response row — the ruling is a typed reference to the recorded ruling row, never prose (management-lane strengthening, adopted: "who signed this constant" is a click, not archaeology), so a correct constant (the band-neutral hover ring) is model-distinguishable from an accidental one, and the undeclared middle — where this bug lived — stops being representable; (3) CENSUS — the family-preservation witness generalized: every (component state × stateful channel) resolves in-family or sits under a declared ConstantByLaw, executed over the emitted CSS, counted, cross-family REDs live. A natural early StandingIntent row (ask once, compile forever). Honesty boundary: decidable for band-var-routed channels (the value names its family); a ratchet for sound until it routes per-band (declared ConstantByLaw meanwhile, never silent). First slice of the composition session. - **Sequencing:** W2-bulk rides AFTER the row archetype lands (pouring ~200 migrated rows into the old layering churns twice). W1h stays parked behind composition, as ruled. + +Composition close-out (2026-07-25, #7234 — all four slices LANDED, each green by execution with REDs live): slice 1 the StateResponse/ResponseScope/census machinery with the press specimen byte-identical (`gunbc.design.state_response`, `dispatch_state_family`, `dispatch_stateful_channels`) · slice 2 the RoadmapRow archetype with the chip-scale actuator derived from row scale (`roadmap_row_archetype`, h-row) · slice 3 page altitude with the declared convergence row re-pointed at #7216 (`gunbc.roadmap_altitude` — the brief's #7162 was superseded; verified before shaping) · slice 4 the loud reason as the disclosure's first line (`.disclosure-reason`, title stays convenience). PROVISIONAL rows (three: h-row 24px, clip-without-ellipsis, gutter space-4) listed in the PR body for one-liner flips; the emitted-CSS census counts zero undeclared response rows; the six person-observable checkpoints await the operator's pass on the live page. - **SESSION GREENLIT (operator, 2026-07-24).** The composition session's brief routes from the management lane, seeded from the flex-head-line cut (133bbfec3, current through 2934b6188); every OPERATOR DECISION row above is drained. The iteration protocol is ADOPTED (operator, same word): decisions drained before a session enters; the worker holds provisional-decision rights mid-session, with every PROVISIONAL mark landing as a counted row so the operator's flip at the pass is a located one-liner (rework-is-first-class applied to design decisions); the management lane reviews receipts before the operator's pass; passes file candidate LAWS, not fixes. The operator's standing gloss on the protocol's purpose: keep stacking feedback and decisions, and root-cause the graph further, more systemically — every signed ruling an authority row plus a witness, so re-litigation dies as a class. ## Workstreams From e6b3ccbfd5d9c13524a5e4ed7c6ed13e03870f47 Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 25 Jul 2026 14:45:49 -0400 Subject: [PATCH 7/7] R4 closed by ear: sound is fine as is (operator, 2026-07-25) The last open review-round item. The resume() fix was the real candidate; the modeled parameters stand un-retuned per the by-ear-first discipline. Co-Authored-By: Claude Fable 5 --- docs/plans/roadmap-workspace-remodel-plan.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plans/roadmap-workspace-remodel-plan.md b/docs/plans/roadmap-workspace-remodel-plan.md index c25317e4279..fafed6fc6c5 100644 --- a/docs/plans/roadmap-workspace-remodel-plan.md +++ b/docs/plans/roadmap-workspace-remodel-plan.md @@ -9,7 +9,7 @@ The operator's review list reduced to four roots; several complaints are one def - **R1 — the belt's wire taxonomy moonlights as the operator UI.** Client renders raw `j.status` as button text; `belt_dispatch_ok_status_labels` is a half-born band model (Ok band only — the class channel has bands, the text channel bypasses them); button `min-width: 25ch` is a projection of the longest *wire label*, so the diagnostic vocabulary shapes the control; sandbox live buttons fuse caption (arm name) with state channel (material), reading as white specimens beside red gallery twins. - **R2 — text and geometry sit outside the register's physics.** PR-A gave transform/color/sound law; `textContent` swaps are instant and lawless (the ~1-frame flash on fast wires); no control-geometry axis exists (`space-1` vertical padding + 25ch → thin strip); the `
` disclosure never entered the component register (UA triangle = debug chrome by omission); state materials paint only a 1px border and rest border ≡ `dispatch-ok` border (`--figure` both) — ok is materially invisible. - **R3 — the roadmap node is one anemic String.** `authored(content: "…")` fuses headline/brief/history/refs; updates concatenate into `content` because no time axis exists (`ts-dispatch-elevate` carries its own GO-LIVE/NEW-GAP history inline — the live specimen); served-page doc links are dead by construction (repo-relative hrefs, no `/docs` route — correct only in GitHub's renderer); `fill_dispatch_brief` stubs its slots because `DispatchBriefFields` is `{node_id, title, repo}` — all the row carries. The template's slot names are the field list for `ts-wf-node-schema`. -- **R4 — sound wiring exists; audibility was never proven.** `sandbox.js` is a twin of the production program (same `gunbcTick` on `pointerdown`). Candidates ranked: first-press swallow (AudioContext created lazily in-handler, no `resume()` — tick scheduled on a suspended clock), sub-audible modeled parameters (5 ms / 2200 Hz / 0.12 peak — envelope REDs prove *not loud*; nothing proves *heard*), persisted mute, Safari policy. +- **R4 — sound wiring exists; audibility was never proven.** `sandbox.js` is a twin of the production program (same `gunbcTick` on `pointerdown`). Candidates ranked: first-press swallow (AudioContext created lazily in-handler, no `resume()` — tick scheduled on a suspended clock), sub-audible modeled parameters (5 ms / 2200 Hz / 0.12 peak — envelope REDs prove *not loud*; nothing proves *heard*), persisted mute, Safari policy. **CLOSED by ear (operator, 2026-07-25, local serve on the composition branch): "sound is fine as is."** The resume() fix (W1d A.2) was the real candidate; the modeled parameters stand un-retuned, exactly as the "re-judged by ear, parameters revisited only then" discipline demanded — they were never revisited because the ear said no need. - **Asset paths are minted, not derived.** `roadmap_dispatch_asset_path = "/assets/roadmap-dispatch.js"` is a single authority row (route + `