Files
teahubxyzandClaude Opus 5.5 2ea7884688 orgchain: the tree (lineage lab keys, act as any node, graduation by claim + countersign)
Start the tree: a fresh 24-word lab root and docs/lineage.md's drawing as keys. Orgs get BIP-85 seeds from their
parent (m/83696968'/39'/0'/24'/i'), wallets are BIP-86 m/86'/1'/account'/0/0, npubs are NIP-06; seats are accounts
1..n of their org's seed; indices are never reused. Act as any node to mine, inscribe or sign with its key.

Graduation retires a leaf org on a derived edge and adopts a fresh root in its place. The handoff is OpenOrdex's
buy-order direction: the recipient builds a 2-in/2-out transaction and signs its fee input first, so its signature
fixes where the inscription goes; the holder then countersigns the inscription input. Both are default signatures,
so chain.js is unchanged. The keyring and open claims save with the lab (optional fields, same storage key).

Tests: BIP-85, BIP-86 and NIP-06 spec vectors plus the handoff and its failure cases (36/36 unit).
tests/tree.spec.js rehearses a full graduation in Chromium at desktop and phone sizes, with layout checks;
tests/flow.spec.js now follows this branch's copy and theme, and waits for a mined block before reloading
(28/28 e2e).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:47:56 -07:00

65 lines
4.3 KiB
JavaScript

import { test, expect } from '@playwright/test';
const outside = page => page.evaluate(() => {
const box = document.getElementById('tree').getBoundingClientRect();
return [...document.querySelectorAll('#tree *')].filter(e => { const r = e.getBoundingClientRect(); return r.width > 0 && (r.right > box.right + 1 || r.left < box.left - 1); })
.map(e => `${e.tagName.toLowerCase()}.${e.className}`).slice(0, 5);
});
test('tree: act as time, birth docscope, graduate it, recipient claims, holder countersigns', async ({ page }) => {
const errors = [];
page.on('pageerror', e => errors.push(e.message));
page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
await page.goto('/');
await page.getByRole('button', { name: 'Start the tree' }).click();
await expect(page.locator('#tree-state')).toHaveText('ACTING AS YOLOPRENEUR');
await expect(page.locator('#wallet-state')).toHaveText('TREE · YOLOPRENEUR');
await expect(page.locator('#tree-list .tree-node')).toHaveCount(10);
await page.getByRole('button', { name: 'Act as time', exact: true }).click();
await expect(page.locator('#tree-state')).toHaveText('ACTING AS TIME');
await page.getByRole('button', { name: 'Mine now' }).click();
await expect(page.locator('#balance')).toHaveText('50.00000000');
const inscribe = async (text, metadata, withParent = false) => {
await page.getByLabel('What will you put on your chain?').fill(text);
await page.getByLabel('JSON metadata (optional, independent of body)').fill(metadata);
await page.getByLabel('Metaprotocol (optional, independent field)').fill('lineage/v0');
if (withParent) await page.getByLabel('Parent inscription (optional, immutable)').selectOption({ index: 1 });
await page.getByRole('button', { name: 'Review inscription' }).click();
await page.getByRole('button', { name: 'Sign & inscribe' }).click();
await page.getByRole('button', { name: 'Mine now' }).click();
await expect(page.locator('#pending')).toHaveText('0');
};
await inscribe('time', '{"lineage":"root","name":"time"}');
await expect(page.locator('#tree-list .tree-node.acting')).toContainText('1 inscription: time (root)');
await inscribe('docscope', '{"lineage":"birth","name":"docscope"}', true);
await expect(page.locator('#tree-list .tree-node.acting')).toContainText('2 inscriptions');
await page.locator('#graduate-node').selectOption({ label: 'docscope' });
await page.getByRole('button', { name: 'Mint its own root' }).click();
await expect(page.locator('#tree-list .tree-node.retired')).toContainText('docscope');
await expect(page.locator('#handoff-to option:checked')).toHaveText('docscope (own root)');
await expect(page.locator('#handoff-inscription option:checked')).toHaveText('docscope (birth) · held by time');
await page.getByRole('button', { name: '1 · Recipient claims' }).click();
await expect(page.locator('#status')).toContainText('plain coin');
await page.getByRole('button', { name: 'Mine a block to the recipient' }).click();
await page.getByRole('button', { name: '1 · Recipient claims' }).click();
await expect(page.locator('.claim')).toHaveCount(1);
await expect(page.locator('.claim .tag')).toHaveText('OPEN');
await expect(page.locator('.claim pre')).toContainText('"kind": "lineage-claim"');
expect(await outside(page)).toEqual([]);
await page.getByRole('button', { name: '2 · Holder countersigns' }).click();
await expect(page.locator('#pending')).toHaveText('1');
await expect(page.locator('.claim')).toHaveCount(0);
await page.getByRole('button', { name: 'Mine now' }).click();
const own = page.locator('#tree-list .tree-node').filter({ hasText: 'ADOPTED' });
await expect(own).toContainText('1 inscription: docscope (birth)');
await expect(own).toContainText('own root (docscope)');
await expect(page.locator('#tree-list .tree-node.acting')).toContainText('1 inscription: time (root)');
await page.reload();
await expect(own).toContainText('1 inscription: docscope (birth)');
await expect(page.locator('#tree-state')).toHaveText('ACTING AS TIME');
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBeTruthy();
expect(await outside(page)).toEqual([]);
await page.locator('#tree').screenshot({ path: `test-results/tree-${test.info().project.name}.png` });
expect(errors).toEqual([]);
});