diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..e4a686b --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,55 @@ +name: Website checks and GitHub Pages + +on: + pull_request: + paths: + - 'website/**' + - '.github/workflows/pages.yml' + push: + branches: [main] + paths: + - 'website/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: '22' + - run: node --check website/script.js + - run: node website/test.cjs + + deploy: + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + needs: check + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + concurrency: + group: github-pages + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + - name: Prepare public files only + run: | + mkdir _site + cp website/index.html website/styles.css website/script.js _site/ + - uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3 + with: + path: _site + - name: Deploy + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/website/README.md b/website/README.md new file mode 100644 index 0000000..e45b490 --- /dev/null +++ b/website/README.md @@ -0,0 +1,34 @@ +# Chalkline explainer site + +Static public-site proposal. No build, dependencies, accounts, uploads, remote assets, analytics, or live model. GitHub Pages publishes the site after approved changes merge to `main`. + +From the repository root, preview with: + +```sh +python3 -m http.server 8000 --bind 127.0.0.1 --directory website +``` + +Open http://127.0.0.1:8000. Python is optional; any static server works. + +`index.html` contains the explanation and demo structure, `styles.css` the responsive styles, and `script.js` the fixed fictional examples. The Pages workflow copies only `index.html`, `styles.css`, and `script.js` into the deployment artifact. Documentation, tests, team references, receipts, reports, and reviewer fixtures are excluded. + +## Demo boundaries + +Harbor and its tasks are fictional. The demo does not run AI, verify facts, consult external references, or record reviewer approval. Its confidence assessment is illustrative and defaults to limited support. Removing a required source withholds the candidate and sets overall confidence to zero. Restoring it shows the candidate again, without implying approval. The site labels receipt functionality as proposed in PR #18 while that work is unmerged. + +## Checks + +```sh +node --check website/script.js +node website/test.cjs +``` + +Node is needed only for these optional developer checks. The test exercises the actual script using a small DOM stub: both tasks, source removal/restoration, minimum scoring, and approval separation. It does not replace browser or accessibility review. + +Browser checks on the local prototype covered desktop and 390px mobile layout, missing-source behavior, restoration, and absence of off-origin resources. No full accessibility audit, screen-reader test, live-model evaluation, or publication review is claimed. Recheck links and PR status before publication. Keep the site explanation aligned with canonical repository contracts; illustrative scores and process exit status never authorize publication. + +## Publishing + +`.github/workflows/pages.yml` runs the JavaScript checks on site pull requests without publishing. After a site or workflow change merges to `main`, it reruns checks and deploys the three public files to https://block.github.io/chalkline/. Manual runs deploy only when run from `main`. Deployments use the `github-pages` environment and GitHub’s short-lived identity token, not a stored publishing secret. + +The repository Pages source must be set to GitHub Actions. Environment approval rules, if configured, still apply. There is no hosted PR preview. The live site becomes available after the first successful deployment. diff --git a/website/index.html b/website/index.html new file mode 100644 index 0000000..3eff053 --- /dev/null +++ b/website/index.html @@ -0,0 +1,68 @@ + + + + + + + Chalkline — a writing system your team owns + + + + + +
+ chalkline + +
+
+ +
+
+

01 / Purpose

+

A writing system your team owns.

+

Chalkline helps your team write down its voice, terminology, and channel rules so agents can consult them before drafting. You review the rules and keep them in editable Markdown files.

+ +
Local site prototype

This local prototype has no live model or uploads. The receipt example shows proposed work in PR #18, not a merged release.

+
+
+

02 / Drift

Keep writing guidance out of scattered prompts

+

When writing guidance is copied into separate prompts for support replies, product messages, and release notes, those copies can drift apart. Chalkline keeps the guidance in one set of references and instructs agents to consult them before drafting, flag missing requirements, and ask about unresolved conflicts. Product facts, deadlines, and promises still need their own sources; writing rules cannot establish them.

+
+
+

03 / Setup

Set up your references

+

Open your own copy of Chalkline in an agent that reads repository instructions and ask:

+
“Set up my writing system.”
+
  1. Answer a few questions.

    The agent asks about your team’s voice and terms, one question at a time. Share only material you have permission to use.

  2. Review the proposed rules.

    Review each proposed rule, including any extracted from your examples, before approving it for your team’s use.

  3. Compare the rewrites.

    Compare rewrites of a sample with and without the references. Decide whether you approve the draft and, separately, whether it should guide similar work.

+

Start privately unless all inputs and outputs are safe to publish. Review changes before choosing whether to commit.

+
+
+

04 / Files

What the files contain

+

Setup produces two to four reference files, depending on the guidance your team needs.

+
voice.md
How you sound, with examples.
terminology.md
Preferred words and non-negotiable terms.
vocabulary.md optional
Maps everyday requests to your team’s terms so agents can understand them without correcting how people speak.
channels.md if needed
Guidance specific to a channel.
+

AGENTS.md tells agents how to consult the system. Approved before-and-after examples show how the guidance applies to a particular audience, channel, and task.

+
+
+

05 / Use

Inspect a draft and its sources

+

Choose a fictional Harbor task to see its facts, writing rules, draft, and receipt. The examples are prewritten, not generated or verified by AI. Removing a required source shows how a blocked task should be reported; nothing is sent to a server.

+
+
Harbor / fictional product UIScripted illustration
+

Choose a task

+ + +

+
+
+
+

06 / Improve

Revise guidance through review

+

If a draft reveals missing or unclear guidance, propose a reference change for the responsible person to review. An accepted edit does not automatically become a rule for everyone. Test changes on new, comparable work without showing the agent the expected rewrite; the sample used during setup cannot establish whether the guidance helps on other tasks.

+

Get started

Use the setup protocol and fictional examples in the repository to build your own references, with reviewers chosen by your team.

Explore Chalkline on GitHub
+
+ +
+
+ + diff --git a/website/script.js b/website/script.js new file mode 100644 index 0000000..95bba43 --- /dev/null +++ b/website/script.js @@ -0,0 +1,80 @@ +'use strict'; +// All content is synthetic, fixed fixture data. No model, network, or storage. +const fixtures = { + reservation: { + label: 'Reservation request', factId: 'F1', + facts: ['A reservation request was submitted.', 'The reservation is not confirmed.'], + rules: ['R1 · Use “reservation request” for the submitted request.', 'R2 · Distinguish submission from confirmation. Do not add a promise or next step.'], + draft: 'Reservation request submitted. Your reservation is not confirmed.', + finding: 'Submission is not confirmation → factual evidence: F1; language rules: R1–R2. No next step or guarantee is added.' + }, + change: { + label: 'Failed change request', factId: 'F2', + facts: ['A request to change a reservation failed.', 'The existing reservation is unchanged.', 'No cause or retry instruction was supplied.'], + rules: ['R1 · Use “change request” for a request to change a reservation.', 'R2 · State failure and the unchanged reservation. Do not invent a cause or retry instruction.'], + draft: 'Change request failed. Your reservation has not changed.', + finding: 'Failure and unchanged reservation are preserved → factual evidence: F2; language rules: R1–R2. Cause and retry instructions are omitted because F2 does not supply them.' + } +}; +let selected = 'reservation'; +let missing = false; +const byId = id => document.getElementById(id); +function addField(container, label, value) { + const row = document.createElement('div'); + const term = document.createElement('dt'); + const description = document.createElement('dd'); + term.textContent = label; + description.textContent = value; + row.append(term, description); + container.append(row); +} +function render() { + const task = fixtures[selected]; + document.querySelectorAll('[data-task]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.task === selected))); + const toggle = byId('missing-toggle'); + toggle.setAttribute('aria-pressed', String(missing)); + toggle.textContent = `Remove required source: ${missing ? 'on' : 'off'}`; + byId('fact-id').textContent = task.factId; + byId('facts').replaceChildren(...task.facts.map(fact => { + const li = document.createElement('li'); li.textContent = fact; return li; + })); + byId('rule-one').textContent = missing ? 'R1 · Required terminology source unavailable. Not consulted in this scenario.' : task.rules[0]; + byId('rule-two').textContent = task.rules[1]; + byId('source-status').textContent = missing ? 'Scenario: required R1 is missing; R2 remains available.' : 'Scenario: R1–R2 supplied; approval and optional calibration coverage are not established.'; + byId('draft').textContent = missing ? '' : task.draft; + byId('draft').hidden = missing; + byId('blocked-message').hidden = !missing; + byId('draft-note').hidden = missing; + byId('draft-status').textContent = missing ? 'Blocked / no draft' : 'Illustration / not approved'; + const fields = byId('receipt-fields'); + fields.replaceChildren(); + const rows = [ + ['Scope', `${task.label}, product UI, fictional Harbor ${missing ? 'requested task' : 'candidate v1'}; excludes real product use and publication approval.`], + ['Sources', `Uncommitted synthetic fixtures in script.js#fixtures.${selected}. Facts: ${task.factId}. Language rules ${missing ? 'available: R2. R1 is missing, not consulted' : 'supplied: R1–R2'}. These labels identify in-page fixtures, not external files or verified provenance.`], + ['Findings', missing ? `Required R1 is absent, as set by the example control. Facts ${task.factId} remain supplied; they do not replace guidance. No draft was assessed.` : task.finding], + ['Exact wording', 'None applicable in this synthetic scenario.'], + ['Missing evidence', `${missing ? 'Required R1 terminology source missing: blocks drafting. ' : ''}Optional calibration pair absent. Rule approval and the authority of the factual source are unconfirmed. No model or independent review was run.`], + ['Unresolved', missing ? 'Restore the required terminology source. Rule approval and fact authority remain unconfirmed.' : 'Rule approval and factual authority remain unconfirmed; no necessary fixture fact is missing.'], + ['Required review', 'Content reviewer: confirm rules, facts, and wording before any use. Actual owner unknown.'], + ['Reviewer approval', 'Not recorded.'] + ]; + rows.forEach(([label, value]) => addField(fields, label, value)); + const dimensions = [ + {name: 'Guidance', score: missing ? 0 : 1, reason: missing ? 'Required terminology source unavailable.' : 'Rules supplied, not verified as approved; optional calibration pair absent.'}, + {name: 'Facts', score: 1, reason: `${task.factId} supplies the fixture claims; the source’s authority has not been confirmed.`}, + {name: 'Verification', score: 1, reason: 'This example assumes an informal reread, not a completed review or independent check.'} + ]; + byId('scores').replaceChildren(...dimensions.map(dimension => { + const box = document.createElement('div'); box.className = 'score'; + const title = document.createElement('strong'); title.textContent = `${dimension.name} · ${dimension.score}`; + const reason = document.createElement('p'); reason.textContent = dimension.reason; + box.append(title, reason); return box; + })); + const overall = Math.min(...dimensions.map(dimension => dimension.score)); + byId('overall').textContent = `Overall ${overall} — ${missing ? 'blocked. Required guidance is missing.' : 'limited. All three dimensions are limited.'}`; + byId('demo-content').hidden = false; + byId('demo-announcement').textContent = `${task.label}. Required source ${missing ? 'absent' : 'available'}. Overall ${overall}, ${missing ? 'blocked. Draft withheld.' : 'limited. Illustrative candidate shown; not approved.'}`; +} +document.querySelectorAll('[data-task]').forEach(button => button.addEventListener('click', () => { selected = button.dataset.task; render(); })); +byId('missing-toggle').addEventListener('click', () => { missing = !missing; render(); }); +render(); diff --git a/website/styles.css b/website/styles.css new file mode 100644 index 0000000..f50812e --- /dev/null +++ b/website/styles.css @@ -0,0 +1,2 @@ +:root{--paper:#f7f6f1;--ink:#242720;--muted:#62655b;--line:#d3d4ca;--wash:#eeeee6;--accent:#455840;--mono:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",monospace;--sans:Arial,Helvetica,sans-serif}*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:32px}body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:17px;line-height:1.6}a{color:inherit;text-underline-offset:4px}a:hover{color:var(--accent)}a:focus-visible,button:focus-visible{outline:3px solid var(--accent);outline-offset:5px}button{font:inherit;cursor:pointer}.skip{position:absolute;left:20px;top:-100px;background:var(--ink);color:var(--paper);padding:12px;z-index:10}.skip:focus{top:10px}.header{max-width:1280px;margin:auto;padding:29px 48px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid var(--line);gap:24px}.wordmark{font-family:var(--mono);font-weight:700;letter-spacing:-1px;text-decoration:none;font-size:23px}.mark{display:inline-grid;place-items:center;width:28px;height:28px;border:1px solid var(--ink);margin-right:8px;font-weight:400}.header nav{display:flex;gap:27px;font-size:13px;font-family:var(--mono)}.header nav a{text-decoration:none}.layout{max-width:1280px;margin:auto;display:grid;grid-template-columns:240px minmax(0,1fr);padding:0 48px;gap:64px}.rail{padding-top:66px;align-self:start;position:sticky;top:0;font-size:12px;font-family:var(--mono)}.eyebrow{font-family:var(--mono);font-size:11px;font-weight:400;letter-spacing:.065em;text-transform:uppercase;line-height:1.65}.rail .eyebrow{margin:0 0 35px;color:var(--muted)}.rail nav{display:flex;flex-direction:column;gap:15px}.rail nav a{text-decoration:none}.rail nav span{color:var(--muted);display:inline-block;width:35px;font-size:11px}.rail-note{border-top:1px solid var(--line);padding-top:24px;max-width:175px;margin-top:40px;color:var(--muted);font-size:11px;line-height:1.8}main{max-width:760px;min-width:0}main>section{border-bottom:1px solid var(--line);padding:62px 0 66px}main>section>.eyebrow{margin:0 0 27px;color:var(--muted)}h1,h2,h3,p{margin-top:0}h1{font-size:clamp(42px,4.9vw,64px);font-weight:400;letter-spacing:-2.8px;line-height:1.08;margin-bottom:28px}h2{font-size:36px;font-weight:400;letter-spacing:-1.2px;line-height:1.2;margin-bottom:24px}h3{font-size:17px;font-weight:500;line-height:1.4;margin-bottom:8px}p{margin-bottom:20px}.hero{padding-top:65px;padding-bottom:43px}.lede{font-size:21px;line-height:1.5;max-width:630px;margin-bottom:30px}.actions{display:flex;align-items:center;gap:24px;flex-wrap:wrap;font-family:var(--mono);font-size:12px}.primary-link{display:inline-flex;align-items:center;justify-content:space-between;gap:24px;background:var(--ink);color:var(--paper);padding:13px 18px;text-decoration:none;font-family:var(--mono);font-size:12px}.primary-link:hover{background:var(--accent);color:white}.project-note{display:grid;grid-template-columns:140px 1fr;gap:20px;margin-top:52px;border-top:1px solid var(--line);padding-top:19px}.project-note p{font-size:12px;color:var(--muted);line-height:1.7;margin:0}.project-note .eyebrow{font-size:10px}.margin-note{border-left:2px solid var(--ink);padding:4px 0 4px 23px;margin-top:30px}.margin-note p{font-size:15px;margin:8px 0 0;max-width:535px}blockquote{margin:30px 0;padding:25px 27px;border:1px solid var(--line);font-family:var(--mono);font-size:19px;background:var(--wash)}.steps{padding:0;list-style:none;counter-reset:steps}.steps li{counter-increment:steps;position:relative;padding:5px 0 9px 39px}.steps li:before{content:counter(steps,decimal-leading-zero);position:absolute;left:0;top:5px;font:12px/1.8 var(--mono);color:var(--muted)}.steps p{font-size:15px}.small{font-size:12px;color:var(--muted);line-height:1.7}.file-list{margin:31px 0}.file-list>div{padding:19px 0;border-top:1px solid var(--line);display:grid;grid-template-columns:210px 1fr;gap:20px}.file-list>div:last-child{border-bottom:1px solid var(--line)}dt{font-weight:400}.file-list dt,code{font-family:var(--mono);font-size:13px}.file-list dt span{display:block;color:var(--muted);font-size:10px}.file-list dd{font-size:14px;margin:0}.demo{margin-top:33px;border:1px solid #bfc3b7;font-size:14px}.demo-top{padding:15px 21px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;gap:12px;align-items:center;background:var(--wash)}.demo-label{font-family:var(--mono);font-size:10px}.controls{padding:22px;border-bottom:1px solid var(--line)}.control-label{font:11px var(--mono);margin:0 0 12px}.task-buttons{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px}button{border:1px solid #a9aea0;background:transparent;padding:10px 12px;color:var(--ink);font:12px/1.45 var(--mono);border-radius:0}button:hover{background:var(--wash)}button[aria-pressed=true]{color:var(--paper);background:var(--ink)}#missing-toggle{font-size:11px;border-style:dashed}.input-grid{display:grid;grid-template-columns:1fr 1fr;border-bottom:1px solid var(--line)}.input-grid>div{padding:22px}.input-grid>div+div{border-left:1px solid var(--line)}.input-grid h3,.candidate h3,.receipt h3{font-size:13px;font-weight:600}.input-grid h3 span{display:block;font:10px/1.8 var(--mono);color:var(--muted);margin-top:4px}.input-grid ul{padding-left:17px;margin:12px 0;font-size:13px}.input-grid li{margin-bottom:8px}.input-grid .small{margin:14px 0 0;font-size:11px}.candidate{padding:24px;background:#fffefa}.candidate-heading,.receipt-heading{display:flex;justify-content:space-between;gap:16px;align-items:baseline;margin-bottom:18px}#draft-status{font:10px/1.5 var(--mono)}#draft{font-size:24px;line-height:1.4;letter-spacing:-.4px;margin:0 0 18px}#blocked-message{margin-bottom:15px;font-size:17px}.candidate .small{margin-bottom:0}.receipt{padding:24px;border-top:1px solid var(--line);background:var(--wash)}.receipt-heading{margin-bottom:8px}.receipt-heading .eyebrow{font-size:9px}.receipt dl{margin:18px 0}.receipt dl>div{display:grid;grid-template-columns:103px 1fr;gap:12px;margin-bottom:13px;font-size:11px;line-height:1.6}.receipt dt{font-family:var(--mono);color:var(--muted)}.receipt dd{margin:0;overflow-wrap:anywhere}.scores{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid #c6c9bf;border-bottom:1px solid #c6c9bf}.score{padding:15px 13px 15px 0}.score+.score{padding-left:13px;border-left:1px solid #c6c9bf}.score strong{display:block;font:13px var(--mono);margin-bottom:9px}.score p{font-size:11px;margin:0;line-height:1.6}.overall{font:13px/1.7 var(--mono);margin:16px 0 8px}.receipt>.small:last-child{font-size:11px;margin:0}.live-status{font:11px/1.7 var(--mono);padding:13px 22px;margin:0;border-top:1px solid var(--line)}.noscript{padding:20px}.closing{border:1px solid var(--line);padding:28px;margin-top:34px}.closing h3{font-size:21px}.closing p{font-size:15px}footer{padding:32px 0 48px;display:flex;flex-wrap:wrap;gap:16px 24px;align-items:center}footer .wordmark{font-size:16px}footer p,footer>a:last-child{font:10px/1.7 var(--mono);color:var(--muted);margin:0}footer>a:last-child{flex-basis:100%}[hidden]{display:none!important}@media(min-width:1450px){.layout{gap:88px}}@media(max-width:1000px){.layout{grid-template-columns:160px minmax(0,1fr);gap:32px;padding:0 32px}.header{padding:24px 32px}.file-list>div{grid-template-columns:170px 1fr}.actions{gap:18px}h1{font-size:50px}.input-grid{grid-template-columns:1fr}.input-grid>div+div{border-left:0;border-top:1px solid var(--line)}}@media(max-width:700px){.header{padding:21px 20px;align-items:flex-start;gap:18px}.header nav{gap:15px;font-size:10px;flex-wrap:wrap;justify-content:flex-end;max-width:190px}.header .wordmark{font-size:19px}.mark{display:none}.layout{display:block;padding:0 20px}.rail{position:static;padding-top:20px}.rail>.eyebrow,.rail-note{display:none}.rail nav{flex-direction:row;overflow:auto;gap:20px;padding-bottom:14px;border-bottom:1px solid var(--line);font-size:11px}.rail nav a{white-space:nowrap}.rail nav span{width:auto;margin-right:5px;font-size:9px}main>section{padding:43px 0}main>section>.eyebrow{margin-bottom:22px}h1{font-size:45px;letter-spacing:-2px}h2{font-size:30px;letter-spacing:-1px}.lede{font-size:19px}.actions{align-items:flex-start;flex-direction:column}.project-note{grid-template-columns:1fr;gap:10px;margin-top:35px}.file-list>div{grid-template-columns:1fr;gap:6px}.file-list dt span{display:inline;margin-left:8px}.demo-top{align-items:flex-start;flex-direction:column;gap:5px}.controls,.input-grid>div,.candidate,.receipt{padding:18px}.receipt-heading{flex-direction:column;gap:0}.receipt dl>div{grid-template-columns:1fr;gap:2px}.scores{grid-template-columns:1fr}.score+.score{border-left:0;border-top:1px solid #c6c9bf;padding-left:0}.score{padding:13px 0}.candidate-heading{flex-wrap:wrap;gap:5px}#draft{font-size:22px}blockquote{padding:20px;font-size:15px}.closing{padding:22px}.live-status{padding:13px 18px}} +@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}} diff --git a/website/test.cjs b/website/test.cjs new file mode 100644 index 0000000..e0a2567 --- /dev/null +++ b/website/test.cjs @@ -0,0 +1,45 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const vm = require('node:vm'); +class Element { + constructor() { this.textContent = ''; this.hidden = false; this.children = []; this.attributes = {}; this.listeners = {}; this.dataset = {}; } + setAttribute(key, value) { this.attributes[key] = value; } + append(...nodes) { this.children.push(...nodes); } + replaceChildren(...nodes) { this.children = nodes; } + addEventListener(event, handler) { this.listeners[event] = handler; } + click() { this.listeners.click(); } +} +const elements = new Map(); +const tasks = ['reservation', 'change'].map(task => { const el = new Element(); el.dataset.task = task; return el; }); +const document = { + getElementById(id) { if (!elements.has(id)) elements.set(id, new Element()); return elements.get(id); }, + querySelectorAll(selector) { assert.equal(selector, '[data-task]'); return tasks; }, + createElement() { return new Element(); } +}; +vm.runInNewContext(fs.readFileSync(`${__dirname}/script.js`, 'utf8'), { document }); +const el = id => document.getElementById(id); +for (const task of tasks) { + task.click(); + assert.equal(task.attributes['aria-pressed'], 'true'); + assert.equal(el('draft').hidden, false); + assert.ok(el('draft').textContent.length > 0); + assert.match(el('overall').textContent, /Overall 1/); + assert.ok(el('receipt-fields').children.some(row => row.children[0].textContent === 'Reviewer approval' && row.children[1].textContent === 'Not recorded.')); + const original = el('draft').textContent; + el('missing-toggle').click(); + assert.equal(el('draft').hidden, true); + assert.equal(el('draft').textContent, ''); + assert.match(el('overall').textContent, /Overall 0/); + assert.equal(el('missing-toggle').attributes['aria-pressed'], 'true'); + el('missing-toggle').click(); + assert.equal(el('draft').hidden, false); + assert.equal(el('draft').textContent, original); + assert.match(el('overall').textContent, /Overall 1/); +} +const html = fs.readFileSync(`${__dirname}/index.html`, 'utf8'); +assert.doesNotMatch(html, /