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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.5.1

- Teach posts to restore returning participants on open with
`pagelike.identity()`, while keeping first-time and signed-out viewers anonymous.
- Show an existing choice in the vote example before another click, and include
a browser check that opening the example writes no contribution.
- Refresh both distributions from Control's verified public skill snapshot.

## 0.5.0

- Extract the creator plugin into its own public, MIT-licensed repository.
Expand Down
2 changes: 1 addition & 1 deletion directory/differ/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "differ",
"version": "0.5.0",
"version": "0.5.1",
"description": "Create, preview, publish and revise interactive social posts.",
"author": {
"name": "Sky Valley",
Expand Down
2 changes: 1 addition & 1 deletion directory/differ/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "differ",
"version": "0.5.0",
"version": "0.5.1",
"description": "Create, preview, publish and revise interactive social posts.",
"author": {
"name": "Sky Valley",
Expand Down
2 changes: 1 addition & 1 deletion directory/differ/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "differ",
"version": "0.5.0",
"version": "0.5.1",
"description": "Create, preview, publish and revise interactive social posts.",
"author": {
"name": "Sky Valley",
Expand Down
25 changes: 15 additions & 10 deletions directory/differ/skills.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@
},
{
"uri": "skill://publish-content/examples/vote/README.md",
"digest": "sha256:136357a5930e8e61fe1f3161db053435d81341526445b0edfda1e815b4cdc2d2",
"size": 1889
"digest": "sha256:d291254b1d3fceea3b3b829a8ff8125b6ed1b1e729bc57576c682a5f9097a3e2",
"size": 2589
},
{
"uri": "skill://publish-content/examples/vote/check-browser.mjs",
"digest": "sha256:44f2d6c31a07f59ae8cb0ed7655d06309b6cffb38081d27baf27cb6b7d8f5b79",
"size": 1819
},
{
"uri": "skill://publish-content/examples/vote/check.mjs",
Expand All @@ -87,8 +92,8 @@
},
{
"uri": "skill://publish-content/examples/vote/content/app.mjs",
"digest": "sha256:e1010cb228523ebdc499d008c15c201b290149dce297192253420dd0e9a3dfc6",
"size": 1952
"digest": "sha256:237ee0de7f0968ec43f19ffc21fd5077e8d0778cf9f49b38d9102a68a167596e",
"size": 2321
},
{
"uri": "skill://publish-content/examples/vote/content/data/votes.html",
Expand All @@ -97,8 +102,8 @@
},
{
"uri": "skill://publish-content/examples/vote/content/index.html",
"digest": "sha256:d44bec3347f4c4536a15fc9633ab9f9b4e255618edb6d498d9a8d08be0f99d1d",
"size": 1131
"digest": "sha256:ce0aa62697568b6f1d683b21748f48d7cb9cc1436f87e07099aabfe03f5d1a57",
"size": 1168
},
{
"uri": "skill://publish-content/examples/vote/content/rules.html",
Expand All @@ -107,8 +112,8 @@
},
{
"uri": "skill://publish-content/examples/vote/content/vote.mjs",
"digest": "sha256:3ce07820bfba8d8792bbea28671d7d835b3e3c5a1011e65ec7215d999bb22502",
"size": 894
"digest": "sha256:1eeac58ec35de44f18b68064a0de2da6e30e13d6486a7c29f197397295f23fa1",
"size": 978
},
{
"uri": "skill://publish-content/references/connector.md",
Expand All @@ -122,8 +127,8 @@
},
{
"uri": "skill://publish-content/references/participation.md",
"digest": "sha256:a4f05532cd9159907852fe1795e12705acd2deb0ec05acf2135fd2385ac43271",
"size": 7620
"digest": "sha256:b8ff86ab2003b6ad5a4f044f420f428d190f2478c64a9576019d17197dc67513",
"size": 8108
},
{
"uri": "skill://publish-content/references/setup.md",
Expand Down
13 changes: 12 additions & 1 deletion directory/differ/skills/publish-content/examples/vote/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# The afternoon question

A complete ordinary HTML/JS post: tea or coffee, with one effective choice per
signed-in identity, changeable over time. No SDK, generated rules or external
signed-in identity, changeable over time. Uses the managed identity SDK, with no generated rules or external
database. Publish only `examples/vote/content/`.

Read `examples/vote/content/index.html`, `app.mjs`, `vote.mjs`, `rules.html` and
Expand All @@ -14,6 +14,11 @@ restricts choices. The browser counts the latest remaining choice for each
trusted identity. Repeated clicks do not increase that identity's effective
vote. This is one vote per identity, not a guarantee about physical people.

On load, `pagelike.identity()` quietly restores an existing post identity. The
UI then marks its latest remaining choice before any click. A new or signed-out
visitor stays anonymous. Opening does not append a vote; `participate()` and
the write remain in the button handler.

History is deliberate: removing the latest choice reveals an earlier remaining
one. Player's Your participation removes individual contributions; removing all
of your choices leaves no vote. The creator can moderate contributions. The UI
Expand All @@ -26,6 +31,12 @@ reads and an actual SSE mutation, two identities, changes, forgery, multi-entry
payloads, whole replacement, cross-person removal, own-removal and moderation.
Run with Node 24+ and `PAGELIKE_BINARY=/absolute/path/to/pagelike`.

Also run `node examples/vote/check-browser.mjs` with that variable and Playwright
installed (or `PLAYWRIGHT_MODULE` pointing to its `index.mjs`). It verifies an
existing choice appears before interaction, opening adds no vote, and a
deliberate change updates the choice. It uses fixture identity with the real
runtime; the platform's separate integration verifies restoration across logins.

These checks have a specific contract. For a new activity, adapt its checks and
prove them against deliberately defective variants before trusting green output.
Browser and Player login checks remain separate. Never point mutation tests at
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import assert from 'node:assert/strict';
import { fileURLToPath } from 'node:url';
import { readBundle, runtime } from '../../scripts/runtime.mjs';
import { launchBrowser, viewer } from '../../scripts/browser.mjs';
import { voteRequest } from './content/vote.mjs';

const host = await runtime(); let browser;
try {
browser = await launchBrowser();
const site = await host.site(await readBundle(fileURLToPath(new URL('./content/', import.meta.url))));
const alice = await site.participate('p_alice');
const saved = await site.request('/data/votes.html', voteRequest('p_alice', 'tea'), alice);
assert.equal(saved.status, 206); await saved.text();
const before = await (await site.request('/data/votes.html')).text();
const returning = await viewer(site, browser, 'p_alice'), guest = await viewer(site, browser, null);
try {
await returning.page.locator('[data-choice="tea"][aria-pressed="true"]').waitFor();
assert.equal(await returning.page.locator('#mine').textContent(), 'Your current choice: tea.');
assert.equal(await guest.page.locator('[aria-pressed="true"]').count(), 0);
assert.equal(await (await site.request('/data/votes.html')).text(), before, 'opening must not add a vote');
await returning.page.getByRole('button', { name: 'Coffee' }).click();
await returning.page.locator('[data-choice="coffee"][aria-pressed="true"]').waitFor();
assert.equal(await returning.page.locator('#totals').textContent(), 'Tea 0 · Coffee 1');
console.log('PASS: returning identity displays its existing vote before interaction; opening writes nothing; an intentional change updates it. Fixture identity, real runtime and browser.');
} finally { await returning.context.close(); await guest.context.close(); }
} finally { if (browser) await browser.close(); await host.close(); }
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { voteRequest, currentVotes } from './vote.mjs';
const status = document.querySelector('#status');
let reading = false, dirty = false;
let participant = null;
async function refresh() {
dirty = true;
if (reading) return;
Expand All @@ -10,8 +11,10 @@ async function refresh() {
dirty = false;
const response = await fetch('/data/votes.html', { cache: 'no-store' });
if (!response.ok) throw Error('The room could not be loaded. Reconnecting…');
const votes = currentVotes(new DOMParser().parseFromString(await response.text(), 'text/html'));
const votes = currentVotes(new DOMParser().parseFromString(await response.text(), 'text/html'), participant);
document.querySelector('#totals').textContent = `Tea ${votes.tea} · Coffee ${votes.coffee}`;
document.querySelector('#mine').textContent = votes.mine ? `Your current choice: ${votes.mine}.` : '';
for (const button of document.querySelectorAll('[data-choice]')) button.setAttribute('aria-pressed', String(button.dataset.choice === votes.mine));
}
} catch (error) { status.textContent = error.message; }
finally { reading = false; }
Expand All @@ -26,7 +29,7 @@ for (const button of document.querySelectorAll('[data-choice]')) button.addEvent
const buttons = [...document.querySelectorAll('button')];
buttons.forEach(item => { item.disabled = true; });
try {
const participant = await pagelike.participate();
participant = await pagelike.participate();
const response = await fetch('/data/votes.html', voteRequest(participant, button.dataset.choice));
if (!response.ok) throw Error(`Your choice was not saved (${response.status}). Please try again.`);
status.textContent = 'Your latest choice counts.';
Expand All @@ -35,3 +38,5 @@ for (const button of document.querySelectorAll('[data-choice]')) button.addEvent
finally { buttons.forEach(item => { item.disabled = false; }); }
});
await refresh();
participant = await pagelike.identity();
if (participant) await refresh();
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<style>body{margin:0;background:#f5efdf;color:#28251f;font:20px system-ui}main{max-width:32rem;padding:2rem;margin:auto}h1{font-size:3rem;line-height:1}button{font:inherit;padding:1rem;margin:.4rem;border:2px solid;border-radius:2rem;background:#fff8e9}button:focus-visible{outline:4px solid #b24e29}output{display:block;margin:2rem 0;font-size:1.5rem}#status{min-height:3em;font-size:1rem}small{display:block;line-height:1.5}</style>
<main><p>The afternoon question</p><h1>Tea or coffee?</h1>
<output id="totals" aria-live="polite">Loading the room…</output>
<p id="mine" aria-live="polite"></p>
<button data-choice="tea">Tea</button><button data-choice="coffee">Coffee</button>
<p id="status" role="status"></p><small>One current choice per signed-in identity. Change your mind whenever you like. Your latest remaining choice counts. Use Player’s Your participation to remove your contributions.</small></main>
<script src="/-/client.js"></script><script type="module" src="./app.mjs"></script></html>
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ export function voteRequest(participant, choice) {
body: `<li data-voter="${escape(participant)}" data-choice="${choice}">${choice}</li>` };
}

export function currentVotes(document) {
export function currentVotes(document, participant = null) {
const latest = new Map();
for (const entry of document.querySelectorAll('#votes > li')) latest.set(entry.dataset.voter, entry.dataset.choice);
return { tea: [...latest.values()].filter(choice => choice === 'tea').length,
coffee: [...latest.values()].filter(choice => choice === 'coffee').length };
coffee: [...latest.values()].filter(choice => choice === 'coffee').length,
mine: participant ? latest.get(participant) ?? null : null };
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@ authored files, rules and seeds. Never download live visitor data into a remix.
Use relative authored asset URLs to stay on the selected version; root URLs
address the current post. Live data always uses root `/data/...` URLs.

Load `<script src="/-/client.js"></script>`. When someone explicitly chooses
Load `<script src="/-/client.js"></script>`. On load, call
`await pagelike.identity()` to quietly restore a returning participant's opaque
post identity, including on another device signed into the same account. It
returns `null` for first-time or signed-out visitors and when identity is
unavailable, without a popup. Use the returned identity to read and mark their
existing entries; restoration never adds a contribution. Keep public reads and
local play usable while it resolves. Do not infer ownership from localStorage.

When someone explicitly chooses
to contribute, call `await pagelike.participate()`. Player handles sign-in and
consent, and the runtime sets its host-only HttpOnly partitioned cookie. The
participation, and the runtime sets its host-only HttpOnly partitioned cookie. The
result is an opaque identity string stable within this post. No email,
handle or cross-post identity is disclosed. Then use same-origin `fetch`:

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "differ-plugin",
"version": "0.5.0",
"version": "0.5.1",
"private": true,
"type": "module",
"engines": {
Expand Down
2 changes: 1 addition & 1 deletion plugins/differ/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "differ",
"version": "0.5.0",
"version": "0.5.1",
"description": "Create, preview, publish and revise interactive social posts.",
"author": {
"name": "Sky Valley",
Expand Down
2 changes: 1 addition & 1 deletion plugins/differ/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "differ",
"version": "0.5.0",
"version": "0.5.1",
"description": "Create, preview, publish and revise interactive social posts.",
"author": {
"name": "Sky Valley",
Expand Down
2 changes: 1 addition & 1 deletion plugins/differ/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "differ",
"version": "0.5.0",
"version": "0.5.1",
"description": "Create, preview, publish and revise interactive social posts.",
"author": {
"name": "Sky Valley",
Expand Down
2 changes: 1 addition & 1 deletion plugins/differ/scripts/differ.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -37138,7 +37138,7 @@ function inspectVideo(input2) {
// package.json
var package_default = {
name: "differ-plugin",
version: "0.5.0",
version: "0.5.1",
private: true,
type: "module",
engines: {
Expand Down
25 changes: 15 additions & 10 deletions plugins/differ/skills.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,13 @@
},
{
"uri": "skill://publish-content/examples/vote/README.md",
"digest": "sha256:136357a5930e8e61fe1f3161db053435d81341526445b0edfda1e815b4cdc2d2",
"size": 1889
"digest": "sha256:d291254b1d3fceea3b3b829a8ff8125b6ed1b1e729bc57576c682a5f9097a3e2",
"size": 2589
},
{
"uri": "skill://publish-content/examples/vote/check-browser.mjs",
"digest": "sha256:44f2d6c31a07f59ae8cb0ed7655d06309b6cffb38081d27baf27cb6b7d8f5b79",
"size": 1819
},
{
"uri": "skill://publish-content/examples/vote/check.mjs",
Expand All @@ -87,8 +92,8 @@
},
{
"uri": "skill://publish-content/examples/vote/content/app.mjs",
"digest": "sha256:e1010cb228523ebdc499d008c15c201b290149dce297192253420dd0e9a3dfc6",
"size": 1952
"digest": "sha256:237ee0de7f0968ec43f19ffc21fd5077e8d0778cf9f49b38d9102a68a167596e",
"size": 2321
},
{
"uri": "skill://publish-content/examples/vote/content/data/votes.html",
Expand All @@ -97,8 +102,8 @@
},
{
"uri": "skill://publish-content/examples/vote/content/index.html",
"digest": "sha256:d44bec3347f4c4536a15fc9633ab9f9b4e255618edb6d498d9a8d08be0f99d1d",
"size": 1131
"digest": "sha256:ce0aa62697568b6f1d683b21748f48d7cb9cc1436f87e07099aabfe03f5d1a57",
"size": 1168
},
{
"uri": "skill://publish-content/examples/vote/content/rules.html",
Expand All @@ -107,8 +112,8 @@
},
{
"uri": "skill://publish-content/examples/vote/content/vote.mjs",
"digest": "sha256:3ce07820bfba8d8792bbea28671d7d835b3e3c5a1011e65ec7215d999bb22502",
"size": 894
"digest": "sha256:1eeac58ec35de44f18b68064a0de2da6e30e13d6486a7c29f197397295f23fa1",
"size": 978
},
{
"uri": "skill://publish-content/references/connector.md",
Expand All @@ -122,8 +127,8 @@
},
{
"uri": "skill://publish-content/references/participation.md",
"digest": "sha256:a4f05532cd9159907852fe1795e12705acd2deb0ec05acf2135fd2385ac43271",
"size": 7620
"digest": "sha256:b8ff86ab2003b6ad5a4f044f420f428d190f2478c64a9576019d17197dc67513",
"size": 8108
},
{
"uri": "skill://publish-content/references/setup.md",
Expand Down
13 changes: 12 additions & 1 deletion plugins/differ/skills/publish-content/examples/vote/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# The afternoon question

A complete ordinary HTML/JS post: tea or coffee, with one effective choice per
signed-in identity, changeable over time. No SDK, generated rules or external
signed-in identity, changeable over time. Uses the managed identity SDK, with no generated rules or external
database. Publish only `examples/vote/content/`.

Read `examples/vote/content/index.html`, `app.mjs`, `vote.mjs`, `rules.html` and
Expand All @@ -14,6 +14,11 @@ restricts choices. The browser counts the latest remaining choice for each
trusted identity. Repeated clicks do not increase that identity's effective
vote. This is one vote per identity, not a guarantee about physical people.

On load, `pagelike.identity()` quietly restores an existing post identity. The
UI then marks its latest remaining choice before any click. A new or signed-out
visitor stays anonymous. Opening does not append a vote; `participate()` and
the write remain in the button handler.

History is deliberate: removing the latest choice reveals an earlier remaining
one. Player's Your participation removes individual contributions; removing all
of your choices leaves no vote. The creator can moderate contributions. The UI
Expand All @@ -26,6 +31,12 @@ reads and an actual SSE mutation, two identities, changes, forgery, multi-entry
payloads, whole replacement, cross-person removal, own-removal and moderation.
Run with Node 24+ and `PAGELIKE_BINARY=/absolute/path/to/pagelike`.

Also run `node examples/vote/check-browser.mjs` with that variable and Playwright
installed (or `PLAYWRIGHT_MODULE` pointing to its `index.mjs`). It verifies an
existing choice appears before interaction, opening adds no vote, and a
deliberate change updates the choice. It uses fixture identity with the real
runtime; the platform's separate integration verifies restoration across logins.

These checks have a specific contract. For a new activity, adapt its checks and
prove them against deliberately defective variants before trusting green output.
Browser and Player login checks remain separate. Never point mutation tests at
Expand Down
Loading
Loading