Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Rule files hold rules; the reasoning behind them lives in `design/`. Where two d

| Document | What it is | Read it when |
|---|---|---|
| [`SPEC.md`](SPEC.md) | The mechanism, v0.146 — tables and numbered rules, each pointing at its reasons as `→ why: R-nnn`. The single source of truth. | First, to understand what the engine does. |
| [`SPEC.md`](SPEC.md) | The mechanism, v0.147 — tables and numbered rules, each pointing at its reasons as `→ why: R-nnn`. The single source of truth. | First, to understand what the engine does. |
| [`SURFACE.md`](SURFACE.md) | What the surface tells a member and what a control does: the event matrix, the marks, the wallets, the founding order, the card kinds. Asserted against the page's own tables by `npm run spec-check`. | Second, to understand what a member sees. |
| [`CLAUDE.md`](CLAUDE.md) | The project's operative reference: the vocabulary, the glossary of every named part, and the post-mortems that bite. | Before contributing. |
| [`design/STYLE.md`](design/STYLE.md) | The surface-copy checklist every string a member can read has to pass. | Before touching `copy.js`. |
Expand Down
4 changes: 2 additions & 2 deletions SPEC.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Group Drafting Engine — Specification v0.146
# Group Drafting Engine — Specification v0.147
### Working name deferred (direction: "draft")

A compiler for group agreement. Input: a starting text, a roster, a constitution file. Output: the most-agreed text, plus a record of every disagreement, ranked and mapped. Institutional acts — provenance, adoption, ratification — belong to the convening context. The tool measures agreement; it does not confer legitimacy.
Expand Down Expand Up @@ -309,7 +309,7 @@ The founding is optional in full: a convenor may set everything and delegate not
| room proposes, convenor answers | — | held |
| delegated | — | — |

How each state reads to a member is copy rather than mechanism: the power clause is `design/STYLE.md` T8's, in `design/copy.js`, and a delegated setting carries no governance sentence at all — the preamble's default applies.
How each state reads to a member is copy rather than mechanism: the power clause is `design/STYLE.md` T8's, in `design/copy.js`, and a delegated setting carries no governance sentence at all — the Founder's own ✒️ and 🛡️ clauses name what the Founder holds (SURFACE §8, F16).

1. **Birth.** Every setting is born convenor-held, both powers, question shut, value unset. → why: R-001
2. **Delegation is an act, one-way, the convenor's own, never a motion.** Before the start, on a delegable setting, it opens the founding question; otherwise — and always after the start — it is a **hand-over**: the value stands, only the holder changes. Each setting's hand-over moment is in §9.7.1 (*hand-over from*). → why: R-006, R-027
Expand Down
71 changes: 35 additions & 36 deletions SURFACE.md

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions design/STYLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Glyph names are stable (tabulated 2026-09-07, Q1208; the founding order's own gl
| visibility | 🌍 | — |
| text | 📝 | 📄 until backlog 204; the applicant's words are 👋 |
| founder-is-member | 🎩 | — |
| proposing gate | 💡 | keeps its glyph on the card titled *Proposals* — Ed's ruling of 2026-09-01 (Q1121), the gate being what the tab and the preamble stack show |
| proposing gate | 💡 | keeps its glyph on the card titled *Proposals* — Ed's ruling of 2026-09-01 (Q1121), the gate being what the tab on the ✏️ clause shows (#223) |
| voting gate | ⚖️ | — |
| crown | 👑 | — |
| horn | 📯 | — |
Expand Down Expand Up @@ -197,8 +197,8 @@ Glyph names are stable (tabulated 2026-09-07, Q1208; the founding order's own gl
you · Ran out of time* — nothing was decided, so nothing ranks it. *Refused*
is the Founder's 🛡️ alone; a wording the room closed early is *Rejected*
(T8). **Nor does a heading-over-text card carry a title head** (Ed,
2026-09-15, Q1373): the grants ✒️ 🛡️ open as the strip, the paragraph and
OK, and 🏛️ 💡 ⚖️ head with the Proposals clause, their rule. The card's name
2026-09-15, Q1373): every grant and gate — ✒️ 🛡️ 🏛️ 💡 ⚖️ — heads with
the clause its tab hangs on, its rule (#223). The card's name
survives on the rail entry, the tab tooltip and the record.

## 5. Bodies and notes
Expand All @@ -208,7 +208,7 @@ Glyph names are stable (tabulated 2026-09-07, Q1208; the founding order's own gl
- **T21 · Read-only copy must survive the spec it summarises.** "Fixed for the
life of the document" predated motions and was false. **The lockline goes
from the settings cards** (1541.14 (a); Q1541 stage 3a): who chose a rule is
its pill (T48), and what changing it takes is the constitution's preamble
its pill (T48), and what changing it takes is the Proposals clauses
and the card's own commits.
- **T22 · Section headings carry no intro prose.**
- **T17 · The price is said in words exactly once**, at the act: *the edit is
Expand Down
23 changes: 15 additions & 8 deletions design/band.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,13 @@ window.BAND = (function () {
// clause is a list rather than a sentence. It carries markup (the avatar, the
// 👑 span), so it rides `ctx.clauseFor` beside `rosterClause()` and never
// `headFor`, which escapes what it is given.
// …and it no longer carries the time (#223, Ed 2026-10-03): the moment
// moved to 🍾's clause in the Schedule
const foundedClause = () =>
(!env.cs ? '' : '<p class="cpv founded">Founded by ' +
(!env.cs ? '' : '<p class="cpv founded">' + esc(PAGE_COPY.clause.foundedLead) +
avHtml(founderInfo()) + ' ' + esc(founderInfo().n.trim() || 'Anonymous') +
// 👑 by any reservation (Ed, Q379 wide); 📯 = holds nothing.
' ' + founderMark() + esc(foundedAt()) + '.</p>');
' ' + founderMark() + '.</p>');
// the close's moment, the same shape as the founding's
const closedAtWords = () => {
if (!env.cs || !env.cs.closed || env.cs.closedAt == null) return '';
Expand Down Expand Up @@ -580,7 +582,7 @@ window.BAND = (function () {
// stacked field. The chips a shape's unit once grew above the field
// left with 🧭 (Q1363).
opt(V, 'ending', 'ends',
'No more changes to the document may be made after ' +
'Proposals may be made until ' +
'<input class="num numin datein" type="datetime-local" data-txt="endsAt" value="' + V.endsAt + '">.') +
rungOpt(V, 'ending', 'perpetual', ENDING_NEVER) +
'</div>'; })(),
Expand Down Expand Up @@ -644,9 +646,9 @@ window.BAND = (function () {
'<option value="' + u + '"' + (u === unit ? ' selected' : '') + '>' + u + '</option>').join('') +
'</select>';
return opt(V, 'rateBy', 'founder',
'Members may make a new proposal ✏️ every ' +
'Members may make a new ✏️ proposal every ' +
'<input class="num numin" type="number" data-num="dripN" min="1" max="2880"' +
(shown === '' ? '' : ' value="' + shown + '"') + '> ' + sel + '.', ''); })() +
(shown === '' ? '' : ' value="' + shown + '"') + '> ' + sel + ', up to 3 at a time.', ''); })() +
'</div>',
chamber: () =>
(() => { const V = ladderView('chamber');
Expand Down Expand Up @@ -1086,7 +1088,9 @@ window.BAND = (function () {
// other seat (Q1503)
if (c.k === 'hat') {
if (stateOf(c, ctx) === 'news') return 'watching';
return amFounder() && !constituted() && docOpen() && founderCanCommit() ? 'setting' : 'watching';
// …answerable before ✒️ is accepted (#223, G6: 🎩 is the walk's second
// step, ✒️ its third), so not pen-gated
return amFounder() && !constituted() && docOpen() ? 'setting' : 'watching';
}
if (!MANAGED_KEYS.includes(c.k)) return null;
if (c.record || c.isGate || c.door || c.kind === 'personal' || c.admit || c.release ||
Expand Down Expand Up @@ -1169,9 +1173,12 @@ window.BAND = (function () {
attrs: ' data-confirm="1"', title: PAGE_COPY.setIt, until: powerParts(c).chosen ? null : 'choose' });
return acts;
}
// **🎩 is answered above the pen** (#223, G6): the walk meets it before
// ✒️ is accepted, so it commits with no power, as ✋ and 🖼️ do (Q980) —
// a ✒️ on it would be a power not yet held (T44)
if (c.k === 'hat') {
const ready = !!S.hatPick && S.hatPick !== hatCurrent() && mayPen() && docOpen();
acts.push({ kind: 'commit', glyph: '✒️', glyphHtml: glyphHtml('✒️'), cls: 'btn-approve emojibtn',
const ready = !!S.hatPick && S.hatPick !== hatCurrent() && docOpen();
acts.push({ kind: 'commit', glyph: '✓', glyphHtml: window.CARDS.TICK, cls: 'btn-approve',
attrs: ' data-confirm="1"', title: PAGE_COPY.setIt, until: ready ? null : 'choose' });
return acts;
}
Expand Down
3 changes: 2 additions & 1 deletion design/begin.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ window.BEGIN = (function () {
const BEGIN_ROWS = [
'title', 'slug', 'chamber',
'admission', 'applications', 'lapse', 'removal', 'invite', 'remove',
'rate', 'ending', 'quorum', 'authorship', 'judgments',
// the Rules' own order since #223 (G6): ⏰ in the Schedule, after 👤 👁️
'rate', 'quorum', 'authorship', 'judgments', 'ending',
'text',
// `machines` is not here (Ed, 2026-09-09: *we are not having machines*):
// it has had no card since 🤖 left the surface (R-078) and stays in the
Expand Down
77 changes: 49 additions & 28 deletions design/copy.js
Original file line number Diff line number Diff line change
Expand Up @@ -997,8 +997,8 @@ window.COPY = (function () {
pw: {
phrase: {
star: { u: 'amend this at will', a: 'refuse proposals that the membership pass' },
invite: { u: 'invite people at will', a: 'refuse invitations and applications that the membership pass' },
remove: { u: 'remove members at will', a: 'refuse removals that the membership pass' },
invite: { u: 'invite people at will', a: 'block invitations that the membership have decided to make' },
remove: { u: 'remove people at will', a: 'block removals that the membership have decided to make' },
text: { u: 'amend the text at will', a: 'refuse changes to the text that the membership pass' },
},
// **On the decision card the phrase names its setting** (Q1429, Ed
Expand All @@ -1025,8 +1025,8 @@ window.COPY = (function () {
// (entry 94) and are only ever the veto sentence's.
noun: {
title: 'the title', slug: 'the link', text: 'the text',
ending: 'the ending', quorum: 'the quorum rule',
authorship: 'the anonymity rule', judgments: 'the vote-reveal rule',
ending: 'the end time', quorum: 'the quorum',
authorship: 'anonymity of proposals', judgments: 'anonymity of votes',
chamber: 'the visibility rule', rate: 'the proposal rate', lapse: 'the lapse rule',
removal: 'the removal rule',
admission: 'the admissions rule', applications: 'the applications rule',
Expand Down Expand Up @@ -1121,19 +1121,38 @@ window.COPY = (function () {
founderRefused: (what) => 'The Founder refused your proposal: ' + what + '.',
couldNotBePut: 'Your proposal could not be put to the membership, and your ✏️ is back in your wallet.',
},
// the Proposals preamble (Y21): the gates' fragments, composed
preamble: {
beforeBegin: 'When the document begins, members may propose changes to rules and vote on proposals.',
voteOnly: 'Members may vote on proposals.',
voteWhenDecided: 'Members may begin voting on proposals when all the rules have been decided.',
proposeAtBegin: ' They may propose changes to rules when the document begins.',
proposeLead: 'Members may propose changes to rules ',
onceAnswered: 'once they have answered the questions the Founder delegated',
asArrive: 'as soon as they arrive',
andVote: ', and may vote on proposals.',
voteTail: '. They may begin voting on proposals when all the rules have been decided.',
passOrdinary: 'A proposal ✏️ passes once it is preferred by enough of the membership, and by more than prefer the current text.',
passConstitutional: 'A constitutional proposal 🏛️ passes only when all members agree.',
// **The Rules' clauses for the grants, the gates and the Schedule** (#223,
// Ed 2026-10-03, G1–G5, every sentence his, verbatim): the ✒️ and 🛡️
// clauses in the lead, the five Proposals clauses, the Schedule's 🍾 and
// 🥂. `list` joins the setting names (`pw.noun`) the way his examples
// read: *the title, the link, the visibility rule and the text*.
clause: {
foundedLead: 'Founded by ',
penAll: 'The Founder may ✒️ change anything at will.',
penSome: (list) => 'The Founder may ✒️ change ' + list + ' at will.',
shieldAll: 'The Founder may 🛡️ refuse any proposal the membership pass.',
shieldSome: (list) => 'The Founder may 🛡️ refuse proposals the membership pass on ' + list + '.',
// at the birth, with no aside: *(that's you!)* is the title clause's alone
// (Ed, 2026-10-03 20:01, on #224, overriding #223's *Before the save*)
penBirth: 'The Founder may ✒️ change anything at will.',
shieldBirth: 'The Founder may 🛡️ refuse proposals the membership pass.',
// *Friday 2 October*, his example's shape: the weekday, then the day
weekDay: (weekday, day) => weekday + ' ' + day,
list: (names) => (names.length < 2 ? names.join('')
: names.slice(0, -1).join(', ') + ' and ' + names[names.length - 1]),
propose: 'Members may propose changes to the text and the rules.',
proposeBefore: 'When the document begins, members may propose changes to the text and the rules.',
vote: 'Members may vote on proposals.',
voteBefore: 'When the document begins, members may vote on proposals.',
rate: (phrase, cap) => 'Members may make a new ✏️ proposal every ' + phrase + ', up to ' + cap + ' at a time.',
constitutional: 'A 🏛️ proposal passes only when all members agree to it.',
beginWaiting: 'The members are waiting on the Founder to begin.',
begun: (time, day) => 'Members started making proposals at ' + time + ' on ' + day + '.',
endingAfter: (when) => 'Proposals may be made until ' + when + '.',
endingNever: 'Proposals may be made indefinitely.',
// *[N] members have signed* — the verb agrees with one (*1 member has*)
closed: (time, day, n) => 'Proposals closed at ' + time + ' on ' + day + '. ' +
n + (n === 1 ? ' member has signed.' : ' members have signed.'),
},
// 👥's rule, in Ed's own words (Q1439, ruling t, 2026-09-18: *A proposal
// cannot pass until it is preferred by at least 50% of the membership
Expand All @@ -1151,10 +1170,12 @@ window.COPY = (function () {
// member's answer card and the composer's lane (Q1137) — and then the
// `(x of y)` that follows *of the membership*. `val.quorumPct` is still
// the one writer of a share where a share is printed whole.
// …and since #223 (G4, Ed 2026-10-03 17:10) it is the pass rule, the one
// clause saying what carries a ✏️ proposal, in his words
quorumRule: {
share: (share, tail) => 'A proposal ✏️ cannot pass until it is preferred by at least ' +
share + ' of the membership' + (tail || '') + '.',
count: (n) => 'A proposal ✏️ cannot pass until it is preferred by at least ' + n + ' members.',
share: (share, tail) => 'A ✏️ proposal passes once at least ' +
share + ' of the membership' + (tail || '') + ' have voted for it and it’s the most preferred option.',
count: (n) => 'A ✏️ proposal passes once at least ' + n + ' members have voted for it and it’s the most preferred option.',
},
// **👥 is the one card that says what its number comes to** (Q1490, Ed
// 2026-09-21 → why: R-139), a deliberate exception to Q1439 ruling u,
Expand Down Expand Up @@ -1357,13 +1378,13 @@ window.COPY = (function () {
gate: {
canpropose: {
title: 'Proposals',
why: 'A proposal is a change you write to the document, for the membership to vote on.',
why: 'A proposal is a change to the document that the membership vote on.',
waiting: 'Waiting on the start.',
done: 'Open — members can propose as soon as they arrive.',
},
canjudge: {
title: 'Voting',
why: 'A vote is your say on a proposal: you are shown two at a time and choose the one you prefer, or neither.',
why: 'A vote is your say on a proposed change to the document.',
waiting: 'Waiting on the rules.',
done: 'Open — the rules are settled.',
},
Expand All @@ -1376,20 +1397,20 @@ window.COPY = (function () {
// and tab read *Accept Constitutional Proposals*, the card's own
// words, and *Activate Your Membership* went
title: 'Constitutional Proposals',
why: 'A 🏛️ is a constitutional proposal: one at a time, returned whole, passing only when all members agree. You are already a member; activating it opens every question, proposal and vote on the rules.',
why: 'A 🏛️ is a constitutional proposal. Members may only make one at a time, and it passes only when all members agree to it.',
waiting: 'Waiting on your arrival.',
// *Accept 🏛️* like every grant (Q1541.24 (a), Ed 2026-09-25),
// replacing Q1502's *Activate 🏛️*
accept: 'Accept 🏛️',
},
pen: {
title: 'Founder Actions',
why: 'As the founder of this document, you have the power to change settings and edit the document at will. Founder Actions are denoted by ✒️. You can give up these powers later if you choose to.',
why: 'As the founder, you have the power to change settings and edit the document at will. Founder Actions are denoted by ✒️. You can give up these powers later if you choose to.',
waiting: 'Waiting on the save.',
},
shield: {
title: 'Founder Veto',
why: 'As the founder of this document, you have the power to veto choices that the membership make. Founder Veto is denoted by 🛡️. You can give up this power later if you choose to.',
why: 'As the founder, you have the power to veto choices that the membership make. Founder Veto is denoted by 🛡️. You can give up this power later if you choose to.',
waiting: 'Waiting on the save.',
},
// **a grant is accepted, not OK'd** (Q1501, Ed 2026-09-22; T44 amended
Expand Down Expand Up @@ -1591,11 +1612,11 @@ window.COPY = (function () {
// until the page reads these, which is a change to the regular page and
// waits for its own pass.
rule: {
endingNever: 'Changes to the document may be made perpetually.',
endingAfter: (when) => 'No more changes to the document may be made after ' + when + '.',
endingNever: 'Proposals may be made indefinitely.',
endingAfter: (when) => 'Proposals may be made until ' + when + '.',
lapseNever: 'Inactive members never lapse and are still counted towards votes.',
lapseAfter: (spell) => 'After ' + spell + ', inactive members lapse and automatically abstain from votes.',
rate: (phrase) => 'Members may make a new proposal ✏️ every ' + phrase + '.',
rate: (phrase, cap) => 'Members may make a new ✏️ proposal every ' + phrase + ', up to ' + cap + ' at a time.',
unit: { days: 'day', hours: 'hour', minutes: 'minute' },
units: (n, unit) => n + ' ' + unit,
address: (slug) => 'The document lives at docs.vote/d/' + slug + '.',
Expand Down
2 changes: 1 addition & 1 deletion design/feed.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
// the floor a race is held to, max(⌈n·E/100⌉, min(2, E)) — product
// before quotient (issue #24), the seconder under it (Q1490)
Math.max(Math.ceil((v.n * roster) / 100), Math.min(2, roster)), roster));
case 'rate': return dripPhrase(v.dripMinutes) ? T.rule.rate(dripPhrase(v.dripMinutes)) : '';
case 'rate': return dripPhrase(v.dripMinutes) ? T.rule.rate(dripPhrase(v.dripMinutes), v.cap) : '';
case 'lapse': return v.afterMs === null ? T.rule.lapseNever : (spell ? T.rule.lapseAfter(spell) : '');
case 'ending': return v.endsAtMs === null ? T.rule.endingNever
: T.rule.endingAfter(C.longWhen(v.endsAtMs)); // the page's words, 24-hour (STYLE T16)
Expand Down
Loading
Loading