Skip to content
Merged
6 changes: 6 additions & 0 deletions dag/extdeps/languages/css/properties.dag
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type CssPropertyName
| FontVariantNumeric
| FontWeight
| Gap
| GridTemplateColumns
| Height
| JustifyContent
| LetterSpacing
Expand All @@ -58,10 +59,12 @@ type CssPropertyName
| Stroke
| TextAlign
| TextDecoration
| TextOverflow
| TextTransform
| Transform
| Transition
| VerticalAlign
| WhiteSpace
| Width
| CustomProperty { name: String }
| RawProperty { name: String }
Expand Down Expand Up @@ -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"
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion dag/extdeps/web_audio/web_audio.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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."

Expand Down
6 changes: 5 additions & 1 deletion dag/gunbc/design/scale.dag
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -124,7 +128,7 @@ data travel_press: ScaleToken = named_length_token(name: "travel-press", px: 1)

data dimension_scale: List<ScaleToken> = [
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,
]

Expand Down
242 changes: 242 additions & 0 deletions dag/gunbc/design/state_response.dag
Original file line number Diff line number Diff line change
@@ -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<StateFamilyMember>
}

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<CssDecl> {
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<ChannelScope>) -> List<CssDecl> {
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<ChannelScope>) -> List<BuildRule> {
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<BuildRule> {
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<ChannelScope>, timing: TimingToken) -> List<BuildRule> {
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<FamilyCensusFault> {
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<ChannelScope>) -> List<StateChannel> {
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<ChannelScope>) -> List<DeclarationRef> {
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<ChannelScope>) -> List<FamilyCensusDefect> {
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<ChannelScope>) -> Int {
count(f.members) * count(per_family_channels(channels: channels))
}

fn family_census_clean(css: String, f: StateFamily, channels: List<ChannelScope>) -> Bool {
count(family_census_defects(css: css, f: f, channels: channels)) == 0
}

fn constant_rulings_all_located(channels: List<ChannelScope>) -> Bool {
all(constant_by_law_rulings(channels: channels), r =>
r.module_path.length() > 0 && r.decl_name.length() > 0)
}
Loading
Loading