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
55 changes: 55 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -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
34 changes: 34 additions & 0 deletions website/README.md
Original file line number Diff line number Diff line change
@@ -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.
68 changes: 68 additions & 0 deletions website/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Chalkline: a writing system your team owns and your agents can consult. Local public-site prototype.">
<title>Chalkline — a writing system your team owns</title>
<link rel="stylesheet" href="styles.css">
<script src="script.js" defer></script>
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="header">
<a class="wordmark" href="#purpose" aria-label="Chalkline home"><span class="mark" aria-hidden="true">/</span> chalkline</a>
<nav aria-label="Main navigation"><a href="#setup">Start</a><a href="#use">Try the example</a><a href="https://github.com/block/chalkline">GitHub <span aria-hidden="true">↗</span></a></nav>
</header>
<div class="layout">
<aside class="rail"><p class="eyebrow">A team-owned writing system</p><nav aria-label="Page sections"><a href="#purpose"><span>01</span> Purpose</a><a href="#drift"><span>02</span> Drift</a><a href="#setup"><span>03</span> Setup</a><a href="#files"><span>04</span> Files</a><a href="#use"><span>05</span> Use</a><a href="#improve"><span>06</span> Improve</a></nav><p class="rail-note">Writing rules in Markdown, reviewed by your team.</p></aside>
<main id="main">
<section id="purpose" class="hero">
<p class="eyebrow">01 / Purpose</p>
<h1>A writing system your team owns.</h1>
<p class="lede">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.</p>
<div class="actions"><a class="primary-link" href="#setup">Set up your writing system <span aria-hidden="true">↓</span></a><a href="https://github.com/block/chalkline">Read the repository ↗</a></div>
<div class="project-note"><span class="eyebrow">Local site prototype</span><p>This local prototype has no live model or uploads. The receipt example shows <a href="https://github.com/block/chalkline/pull/18">proposed work in PR #18</a>, not a merged release.</p></div>
</section>
<section id="drift">
<p class="eyebrow">02 / Drift</p><h2>Keep writing guidance out of scattered prompts</h2>
<p>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.</p>
</section>
<section id="setup">
<p class="eyebrow">03 / Setup</p><h2>Set up your references</h2>
<p>Open your own copy of Chalkline in an agent that reads repository instructions and ask:</p>
<blockquote>“Set up my writing system.”</blockquote>
<ol class="steps"><li><h3>Answer a few questions.</h3><p>The agent asks about your team’s voice and terms, one question at a time. Share only material you have permission to use.</p></li><li><h3>Review the proposed rules.</h3><p>Review each proposed rule, including any extracted from your examples, before approving it for your team’s use.</p></li><li><h3>Compare the rewrites.</h3><p>Compare rewrites of a sample with and without the references. Decide whether you approve the draft and, separately, whether it should guide similar work.</p></li></ol>
<p class="small">Start privately unless all inputs and outputs are safe to publish. Review changes before choosing whether to commit.</p>
</section>
<section id="files">
<p class="eyebrow">04 / Files</p><h2>What the files contain</h2>
<p>Setup produces two to four reference files, depending on the guidance your team needs.</p>
<dl class="file-list"><div><dt>voice.md</dt><dd>How you sound, with examples.</dd></div><div><dt>terminology.md</dt><dd>Preferred words and non-negotiable terms.</dd></div><div><dt>vocabulary.md <span>optional</span></dt><dd>Maps everyday requests to your team’s terms so agents can understand them without correcting how people speak.</dd></div><div><dt>channels.md <span>if needed</span></dt><dd>Guidance specific to a channel.</dd></div></dl>
<p><code>AGENTS.md</code> tells agents how to consult the system. Approved before-and-after examples show how the guidance applies to a particular audience, channel, and task.</p>
</section>
<section id="use" class="demo-section">
<p class="eyebrow">05 / Use</p><h2>Inspect a draft and its sources</h2>
<p>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.</p>
<div class="demo">
<div class="demo-top"><span class="eyebrow">Harbor / fictional product UI</span><span class="demo-label">Scripted illustration</span></div>
<div class="controls"><p class="control-label" id="task-label">Choose a task</p><div class="task-buttons" role="group" aria-labelledby="task-label"><button type="button" data-task="reservation" aria-pressed="true">Reservation request</button><button type="button" data-task="change" aria-pressed="false">Failed change request</button></div><button type="button" id="missing-toggle" aria-pressed="false">Remove required source: off</button></div>
<noscript><p class="noscript">Enable JavaScript to use this local scripted illustration. No draft or assessment is available without it.</p></noscript>
<div id="demo-content" hidden>
<div class="input-grid"><div><h3>Supplied facts <span id="fact-id"></span></h3><ul id="facts"></ul></div><div><h3>Applicable rules <span>R1–R2</span></h3><ul><li id="rule-one"></li><li id="rule-two"></li></ul><p id="source-status" class="small"></p></div></div>
<div class="candidate"><div class="candidate-heading"><h3>Candidate draft</h3><span id="draft-status"></span></div><p id="draft"></p><p id="blocked-message" hidden>The required terminology source is missing. Restore it before drafting.</p><p id="draft-note" class="small">Example copy; not approved for use.</p></div>
<section class="receipt" aria-labelledby="receipt-title"><div class="receipt-heading"><h3 id="receipt-title">Usage receipt</h3><span class="eyebrow">Self-report / separate from copy</span></div><p class="small">This example receipt shows what an agent would report. It does not record an actual review or approval.</p><dl id="receipt-fields"></dl><div class="scores" id="scores"></div><p class="overall" id="overall"></p><p class="small">0 blocked · 1 limited · 2 supported · 3 corroborated. Use the lowest applicable score, never an average. The score describes the evidence available for this task; it does not establish correctness, compliance, or permission to publish.</p></section>
</div>
<p class="live-status" id="demo-announcement" role="status" aria-live="polite" aria-atomic="true"></p>
</div>
</section>
<section id="improve">
<p class="eyebrow">06 / Improve</p><h2>Revise guidance through review</h2>
<p>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.</p>
<div class="closing"><h3>Get started</h3><p>Use the setup protocol and fictional examples in the repository to build your own references, with reviewers chosen by your team.</p><a class="primary-link" href="https://github.com/block/chalkline">Explore Chalkline on GitHub <span aria-hidden="true">↗</span></a></div>
</section>
<footer><a class="wordmark" href="#purpose">chalkline</a><p>Independent local prototype · No remote assets or telemetry</p><a href="https://github.com/block/chalkline/pull/18">PR #18: proposed receipt work ↗</a></footer>
</main>
</div>
</body>
</html>
80 changes: 80 additions & 0 deletions website/script.js
Original file line number Diff line number Diff line change
@@ -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();
Loading
Loading