2026-10-14 17:28:33 -07:00
import { test , expect } from '@playwright/test' ;
async function open ( page ) {
const problems = [];
page . on ( 'console' , m => { if ( m . type () === 'error' ) problems . push ( m . text ()); });
page . on ( 'pageerror' , e => problems . push ( String ( e )));
await page . goto ( './' );
await expect ( page . getByRole ( 'heading' , { name : 'Orgchain' })). toBeVisible ();
return problems ;
}
const say = page => page . locator ( '#say' );
const row = ( page , name ) => page . locator ( '#tree-rows .sel' ). filter ({ has : page . locator ( '.l' , { hasText : new RegExp ( `^ ${ name } $` ) }) });
const verb = ( page , name ) => page . locator ( `[data-verb=" ${ name } "]` ). click ();
const submit = ( page , form ) => page . locator ( `#f- ${ form } [type="submit"]` ). click ();
async function found ( page , name ) { await page . locator ( '#found-name' ). fill ( name ); await submit ( page , 'found' ); }
async function birth ( page , name , kind = 'org' ) {
await verb ( page , 'birth' );
await page . locator ( '#birth-name' ). fill ( name );
await page . locator ( `#f-birth input[value=" ${ kind } "]` ). check ();
await submit ( page , 'birth' );
}
2026-10-14 23:16:03 -07:00
async function pay ( page , to , amount ) {
await verb ( page , 'send' );
await page . locator ( '#send-to' ). selectOption ({ label : to });
await page . locator ( '#send-amount' ). fill ( amount );
await submit ( page , 'send' );
}
2026-10-14 17:28:33 -07:00
const slide = ( page , beat ) => page . locator ( '#beat' ). evaluate (( el , v ) => { el . value = v ; el . dispatchEvent ( new Event ( 'input' , { bubbles : true })); }, String ( beat ));
test ( 'found a holding, then birth, award, decide, graduate and close at the default tempo' , async ({ page }) => {
const problems = await open ( page );
await found ( page , 'Acme Holdings' );
await expect ( say ( page )). toHaveText ( 'acme-holdings is founded, at beat 2.' );
await birth ( page , 'studio' );
await expect ( say ( page )). toHaveText ( 'studio is born to acme-holdings, at beat 3.' );
await row ( page , 'studio' ). click ();
2026-10-14 23:16:03 -07:00
await verb ( page , 'birth' );
await expect ( page . locator ( '#act-why' )). toHaveText ( 'studio has no test BTC for gas. acme-holdings can send some.' );
await row ( page , 'acme-holdings' ). click ();
await pay ( page , 'acme-holdings / studio' , '5' );
await expect ( say ( page )). toHaveText ( 'acme-holdings sent 5 tBTC to studio, at beat 4.' );
await row ( page , 'studio' ). click ();
2026-10-14 17:28:33 -07:00
await birth ( page , 'bot' , 'agent' );
2026-10-14 23:16:03 -07:00
await expect ( say ( page )). toHaveText ( 'bot is born to studio, at beat 5.' );
2026-10-14 17:28:33 -07:00
await verb ( page , 'award' );
await page . locator ( '#award-to' ). selectOption ({ label : 'acme-holdings / studio / bot (agent)' });
await page . locator ( '#award-skill' ). fill ( 'launch' );
await page . locator ( '#award-belt' ). selectOption ( 'green' );
2026-10-15 15:48:31 -07:00
await expect ( page . locator ( '#award-note' )). toContainText ( "The belt is inscribed on bot's own sat, so it goes wherever bot goes and never sells apart from it" );
await expect ( page . locator ( '#act-gas' )). toHaveText ( "Gas: 1,000 sats from studio's tank (0.000075 tBTC), which tops up from its balance." );
2026-10-14 17:28:33 -07:00
await submit ( page , 'award' );
2026-10-15 15:48:31 -07:00
await expect ( say ( page )). toHaveText ( 'bot holds a green belt in launch, at beat 6, on its own sat.' );
2026-10-14 17:28:33 -07:00
await verb ( page , 'decide' );
await page . locator ( '#decide-title' ). fill ( 'Ship weekly' );
await submit ( page , 'decide' );
2026-10-14 23:16:03 -07:00
await expect ( say ( page )). toHaveText ( 'studio decided “Ship weekly”, at beat 7.' );
2026-10-14 17:28:33 -07:00
await verb ( page , 'graduate' );
await page . locator ( '#graduate-go' ). click ();
2026-10-15 00:44:59 -07:00
await expect ( say ( page )). toHaveText ( "studio graduates from acme-holdings, at beat 8. It's a holding of its own now." );
2026-10-14 17:28:33 -07:00
await expect ( row ( page , 'studio' )). toContainText ( 'graduated' );
2026-10-15 00:44:59 -07:00
await expect ( page . locator ( '#holdings-list' )). toContainText ( 'from acme-holdings at beat 8' );
await expect ( row ( page , 'bot' )). toHaveCount ( 0 ); // its seats went with it
2026-10-14 17:28:33 -07:00
await row ( page , 'acme-holdings' ). click ();
await verb ( page , 'close' );
await page . locator ( '#close-go' ). click ();
await expect ( page . locator ( '#close-go' )). toHaveText ( 'Yes, close acme-holdings' );
await page . locator ( '#close-go' ). click ();
2026-10-14 23:16:03 -07:00
await expect ( say ( page )). toHaveText ( 'acme-holdings is closed, at beat 9.' );
2026-10-14 17:28:33 -07:00
await expect ( row ( page , 'acme-holdings' )). toContainText ( 'closed' );
await expect ( row ( page , 'studio' )). not . toContainText ( 'closed' ); // graduated: it holds its own root
await page . locator ( '#timeline-list .ev' , { hasText : 'Ship weekly' }). click ();
await expect ( page . locator ( '#inspect-body' )). toContainText ( '"op": "decide"' );
await expect ( page . locator ( '#inspect-body' )). toContainText ( 'applied' );
2026-10-15 00:44:59 -07:00
await row ( page , 'studio' ). click ();
await page . locator ( '#inspect-body' ). getByRole ( 'button' , { name : 'Open studio' }). click ();
await expect ( page . locator ( '#tree-sub' )). toContainText ( 'studio' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'graduate studio from acme-holdings' );
await row ( page , 'bot' ). click ();
await verb ( page , 'birth' );
2026-10-15 12:15:48 -07:00
await expect ( page . locator ( '#act-why' )). toHaveText ( 'Agents give birth only once they graduate.' );
2026-10-14 17:28:33 -07:00
expect ( problems ). toEqual ([]);
});
test ( 'a timed tempo cues acts; Beat now and the clock land them' , async ({ page }) => {
const problems = await open ( page );
await found ( page , 'acme' );
await page . locator ( '[data-tempo="10"]' ). click ();
await birth ( page , 'a' );
await expect ( say ( page )). toHaveText ( 'a is born to acme, cued for the next beat.' );
await birth ( page , 'b' );
await expect ( page . locator ( '#clock-cued' )). toContainText ( '2 cued · next beat in' );
await expect ( row ( page , 'b' )). toContainText ( 'cued' );
await page . locator ( '#beat-now' ). click ();
2026-10-14 22:25:38 -07:00
await expect ( say ( page )). toHaveText ( 'Beat 3: 2 cued acts landed, and acme earned 50 tBTC.' );
2026-10-14 17:28:33 -07:00
await birth ( page , 'c' );
2026-10-14 22:25:38 -07:00
await expect ( say ( page )). toHaveText ( 'Beat 4: 1 cued act landed, and acme earned 50 tBTC.' , { timeout : 15_000 });
2026-10-14 17:28:33 -07:00
await page . locator ( '[data-tempo="manual"]' ). click ();
await birth ( page , 'd' );
await expect ( page . locator ( '#clock-cued' )). toHaveText ( '1 cued · waiting for Beat now' );
await page . locator ( '[data-tempo="each"]' ). click ();
await expect ( page . locator ( '#clock-cued' )). toHaveText ( 'nothing cued' );
await expect ( page . locator ( '#clock-beat' )). toHaveText ( '5' );
expect ( problems ). toEqual ([]);
});
2026-10-14 23:16:03 -07:00
test ( 'beats pay the holdings, every act pays its gas, and test BTC moves across holdings' , async ({ page }) => {
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' ), why = page . locator ( '#act-why' );
2026-10-14 22:25:38 -07:00
await found ( page , 'acme' );
await expect ( row ( page , 'acme' )). toContainText ( '50 tBTC' );
await expect ( page . locator ( '#clock-pays' )). toHaveText ( 'beats pay acme' );
await birth ( page , 'ops' );
await row ( page , 'ops' ). click ();
2026-10-14 23:16:03 -07:00
await verb ( page , 'birth' );
await expect ( why ). toHaveText ( 'ops has no test BTC for gas. acme can send some.' );
2026-10-14 22:25:38 -07:00
await verb ( page , 'send' );
2026-10-14 23:16:03 -07:00
await expect ( why ). toHaveText ( 'ops has no test BTC to send yet. acme can send some.' );
2026-10-14 22:25:38 -07:00
await row ( page , 'acme' ). click ();
await verb ( page , 'send' );
2026-10-14 23:16:03 -07:00
await expect ( page . locator ( '#send-has' )). toHaveText ( '99.999895 tBTC' );
2026-10-14 22:25:38 -07:00
await expect ( page . locator ( '#act-key' )). toHaveText ( "Signs as acme: acme root · m/84'/1'/0'/0/0" );
2026-10-14 23:16:03 -07:00
await expect ( page . locator ( '#act-gas' )). toHaveText ( "Fee: 500 sats, from acme's balance." );
await pay ( page , 'acme / ops' , '12.5' );
await expect ( say ( page )). toHaveText ( 'acme sent 12.5 tBTC to ops, at beat 4.' );
2026-10-14 22:25:38 -07:00
await expect ( row ( page , 'ops' )). toContainText ( '12.5 tBTC' );
await row ( page , 'ops' ). click ();
2026-10-14 23:16:03 -07:00
await verb ( page , 'birth' );
await expect ( page . locator ( '#act-gas' )). toHaveText ( "Gas: 2,500 sats from ops's tank (0 tBTC), which tops up from its balance." );
await birth ( page , 'bot' , 'agent' );
await expect ( say ( page )). toHaveText ( 'bot is born to ops, at beat 5.' );
await expect ( row ( page , 'ops' )). toContainText ( '≈12.5 tBTC' );
2026-10-14 22:25:38 -07:00
await verb ( page , 'send' );
await page . locator ( '#send-to' ). selectOption ({ label : 'acme / ops / bot (agent)' });
await page . locator ( '#send-max' ). click ();
2026-10-14 23:16:03 -07:00
await expect ( page . locator ( '#send-amount' )). toHaveValue ( '12.49989' );
2026-10-14 22:25:38 -07:00
await page . locator ( '#send-amount' ). fill ( '1' );
await submit ( page , 'send' );
await expect ( say ( page )). toHaveText ( 'ops sent 1 tBTC to bot, at beat 6.' );
await expect ( row ( page , 'bot' )). toContainText ( '1 tBTC' );
await page . locator ( '#timeline-list .ev' , { hasText : 'ops → bot' }). click ();
2026-10-14 23:16:03 -07:00
for ( const text of [ '500 sats, retired' , '11.49989 tBTC back to ops' , "ops's SegWit key (BIP-143, SIGHASH_ALL)" ]) await expect ( inspect ). toContainText ( text );
await page . locator ( '#timeline-list .ev' , { hasText : "ops's gas tank" }). click ();
await expect ( inspect ). toContainText ( "ops's gas tank, at its Taproot key" );
2026-10-14 22:25:38 -07:00
await page . locator ( '#beat-now' ). click ();
await expect ( say ( page )). toHaveText ( 'Beat 7: acme earned 50 tBTC.' );
2026-10-14 23:16:03 -07:00
await found ( page , 'beta' );
await expect ( say ( page )). toHaveText ( 'beta is founded, at beat 8.' );
await expect ( page . locator ( '#clock-pays' )). toHaveText ( 'beats pay 2 holdings, split by hashrate' );
await expect ( page . locator ( '#holdings-list' )). toContainText ( '50% of each beat' );
await page . getByRole ( 'button' , { name : "Raise acme's hashrate" }). click ();
await expect ( page . locator ( '#holdings-list' )). toContainText ( '67% of each beat' );
await page . locator ( '#beat-now' ). click ();
await expect ( say ( page )). toHaveText ( 'Beat 9: acme earned ≈33.33 tBTC, beta ≈16.67 tBTC.' );
await page . locator ( '[data-rule="last"]' ). click ();
await expect ( page . locator ( '#clock-pays' )). toHaveText ( 'beats pay the holding that acts' );
await page . locator ( '#beat-now' ). click ();
await expect ( say ( page )). toHaveText ( 'Beat 10: beta earned 50 tBTC.' );
await pay ( page , 'acme / ops' , '2' );
await expect ( say ( page )). toHaveText ( 'beta sent 2 tBTC to acme / ops, at beat 11.' );
await page . locator ( '#holdings-list .hold' , { hasText : 'acme' }). click ();
await expect ( row ( page , 'ops' )). toContainText ( '≈13.5 tBTC' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( '2 tBTC beta → ops' );
2026-10-14 22:25:38 -07:00
await page . locator ( '[data-tempo="manual"]' ). click ();
2026-10-14 23:16:03 -07:00
await pay ( page , 'acme / ops / bot (agent)' , '0.25' );
2026-10-14 22:25:38 -07:00
await expect ( say ( page )). toHaveText ( 'acme sent 0.25 tBTC to bot, cued for the next beat.' );
await expect ( page . locator ( '#clock-cued' )). toHaveText ( '1 cued · waiting for Beat now' );
await page . locator ( '#beat-now' ). click ();
2026-10-14 23:16:03 -07:00
await expect ( say ( page )). toHaveText ( 'Beat 12: 1 cued send landed, and acme earned 50 tBTC.' );
2026-10-14 22:25:38 -07:00
await expect ( row ( page , 'bot' )). toContainText ( '1.25 tBTC' );
2026-10-14 23:16:03 -07:00
await slide ( page , 3 );
2026-10-14 22:25:38 -07:00
await expect ( row ( page , 'ops' )). not . toContainText ( 'tBTC' );
2026-10-14 23:16:03 -07:00
await expect ( row ( page , 'acme' )). toContainText ( '≈100 tBTC' );
2026-10-14 22:25:38 -07:00
await row ( page , 'acme' ). click ();
2026-10-14 23:16:03 -07:00
await expect ( inspect ). toContainText ( 'as of beat 3' );
2026-10-14 22:25:38 -07:00
await page . locator ( '#to-now' ). click ();
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
expect ( problems ). toEqual ([]);
});
2026-10-14 17:28:33 -07:00
test ( 'the sample replays, any beat can be inspected, and the lab survives a reload' , async ({ page }) => {
const problems = await open ( page );
await page . locator ( '#sample' ). click ();
await expect ( say ( page )). toContainText ( 'Loaded the yolopreneur sample' , { timeout : 20_000 });
await expect ( page . locator ( '#tree-rows .sel' )). toHaveCount ( 12 );
await expect ( page . locator ( '#beat-label' )). toHaveText ( 'Now · beat 7' );
await expect ( row ( page , 'lineage' )). toContainText ( 'graduated' );
2026-10-14 22:25:38 -07:00
await expect ( row ( page , 'time' )). toContainText ( '≈38.5 tBTC' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( '40 tBTC yolopreneur → time' );
2026-10-14 17:28:33 -07:00
await expect ( page . locator ( '#sample' )). toBeDisabled ();
await slide ( page , 3 );
await expect ( page . locator ( '#beat-label' )). toHaveText ( 'Beat 3 of 7' );
await expect ( page . locator ( '#tree-rows .sel' )). toHaveCount ( 3 );
await expect ( page . locator ( '#act-past' )). toBeVisible ();
await page . locator ( '#replay' ). click ();
await expect ( page . locator ( '#beat-label' )). toHaveText ( 'Now · beat 7' , { timeout : 15_000 });
await page . locator ( '#timeline-list .ev' , { hasText : 'graduate lineage from well-known' }). click ();
await expect ( page . locator ( '#inspect-body' )). toContainText ( '"op": "graduate"' );
2026-10-14 18:22:22 -07:00
await expect ( page . locator ( '#inspect-body' )). toContainText ( "well-known's key" );
await row ( page , 'lineage' ). click ();
2026-10-14 19:49:05 -07:00
await expect ( page . locator ( '#inspect-body' )). toContainText ( 'lineage root · account 0' );
await expect ( page . locator ( '#inspect-body' )). toContainText ( "BIP-86 at m/86'/1'/0'/0/0" );
2026-10-14 18:22:22 -07:00
await expect ( page . locator ( '#inspect-body' )). toContainText ( 'its own key' );
await row ( page , 'time' ). click ();
2026-10-14 19:49:05 -07:00
await expect ( page . locator ( '#act-key' )). toHaveText ( "Signs as time: yolopreneur root › 1' · m/86'/1'/0'/0/0" );
2026-10-14 19:09:50 -07:00
const inspect = page . locator ( '#inspect-body' );
for ( const text of [ 'ssh-ca' , 'lineage-ca@time.yolopreneur' , 'mkey:' ]) await expect ( inspect ). toContainText ( text );
await row ( page , 'sol' ). click ();
2026-10-15 16:10:18 -07:00
await expect ( inspect ). toContainText ( "from time, beat 6 · ♻️ on sol's own sat, re-inscribed over white (beat 5)" ); // one sat, its whole ladder
2026-10-14 19:09:50 -07:00
const fact = name => inspect . locator ( '.f' ). filter ({ has : page . locator ( 'dt' , { hasText : new RegExp ( `^ ${ name } $` ) }) });
2026-10-14 19:49:05 -07:00
for ( const [ name , text ] of [[ 'Taproot' , 'bcrt1p' ], [ 'Taproot' , "BIP-86 at m/86'/1'/2'/0/0" ], [ 'SegWit' , 'bcrt1q' ], [ 'SegWit' , "BIP-84 at m/84'/1'/2'/0/0" ],
[ 'npub' , 'npub1' ], [ 'npub' , "NIP-06 at m/44'/1237'/2'/0/0" ], [ 'From' , "yolopreneur root › 1' · account 2" ]]) await expect ( fact ( name )). toContainText ( text );
2026-10-14 19:09:50 -07:00
for ( const text of [ 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5' , 'sol@time.yolopreneur namespaces="git"' , 'sol._domainkey.time.yolopreneur. IN TXT "v=DKIM1; k=ed25519; p=' , 'PublicKey = ' ]) {
2026-10-15 10:48:28 -07:00
await expect ( inspect . locator ( '.code' , { hasText : 'Public lines to paste' }). locator ( 'pre' )). toContainText ( text );
2026-10-14 19:09:50 -07:00
}
2026-10-14 20:14:11 -07:00
await expect ( fact ( 'Tapkey' )). toContainText ( 'bcrt1p' );
await expect ( page . locator ( '#reveal' )). toHaveAttribute ( 'aria-expanded' , 'false' );
await page . locator ( '#reveal' ). click ();
const secrets = inspect . locator ( '.sb' );
for ( const text of [ 'nsec1' , 'BEGIN OPENSSH PRIVATE KEY' , 'PrivateKey = ' , 'account 2 of time' ]) await expect ( secrets ). toContainText ( text );
await expect ( secrets ). toContainText ( /tr\(c[1-9A-HJ-NP-Za-km-z]{51}\)#[a-z0-9]{8}/ );
await secrets . locator ( '.cp' ). first (). click ();
await expect ( secrets . locator ( '.cp' ). first ()). toHaveText ( /Copied|Selected/ );
await row ( page , 'time' ). click ();
await expect ( page . locator ( '#reveal' )). toHaveAttribute ( 'aria-expanded' , 'false' );
await page . locator ( '#reveal' ). click ();
await expect ( secrets ). toContainText ( "Seed · yolopreneur root › 1'" );
await expect ( secrets ). toContainText ( 'BEGIN PRIVATE KEY' );
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
await page . locator ( '#reveal' ). click ();
await expect ( secrets ). toHaveCount ( 0 );
2026-10-14 17:28:33 -07:00
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
await page . reload ();
await expect ( page . locator ( '#tree-rows .sel' )). toHaveCount ( 12 );
await page . locator ( '#theme' ). click ();
await expect ( page . locator ( 'html' )). toHaveAttribute ( 'data-theme' , 'light' );
await page . locator ( '#theme' ). click ();
await page . reload ();
await expect ( page . locator ( 'html' )). toHaveAttribute ( 'data-theme' , 'dark' );
await expect ( page . locator ( '#theme' )). toHaveText ( 'Theme: dark' );
await page . locator ( '#reset' ). click ();
await page . locator ( '#confirm button' , { hasText : 'Erase' }). click ();
await expect ( say ( page )). toHaveText ( 'Erased. This is a fresh lab.' );
await expect ( page . locator ( '#holdings-list li' )). toHaveCount ( 0 );
expect ( problems ). toEqual ([]);
});
2026-10-15 00:32:29 -07:00
test ( 'a node makes an item, another holding bids on it, the holder accepts; on mainnet holdings are funded' , async ({ page }) => {
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' ), why = page . locator ( '#act-why' );
const hold = name => page . locator ( '#holdings-list .hold' , { has : page . locator ( '.hn' , { hasText : new RegExp ( `^ ${ name } $` ) }) });
await found ( page , 'acme' );
await expect ( say ( page )). toHaveText ( 'acme is founded, at beat 2.' );
2026-10-15 14:51:52 -07:00
await verb ( page , 'offer' );
await expect ( why ). toHaveText ( 'Nothing to buy yet. Items come from Make, and orgs and holdings change hands from other trees.' );
2026-10-15 00:32:29 -07:00
await found ( page , 'beta' );
await expect ( say ( page )). toHaveText ( 'beta is founded, at beat 3.' );
await hold ( 'acme' ). click ();
await verb ( page , 'make' );
await expect ( page . locator ( '#f-make .hint' )). toContainText ( 'public and permanent' );
await page . locator ( '#make-name' ). fill ( 'Kit' );
await page . locator ( '#make-note' ). fill ( 'A starter kit.' );
await submit ( page , 'make' );
await expect ( say ( page )). toHaveText ( 'acme made kit, at beat 4.' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'make kit · acme' );
await expect ( inspect ). toContainText ( 'kit · A starter kit. (made at beat 4)' );
await hold ( 'beta' ). click ();
await verb ( page , 'offer' );
await expect ( page . locator ( '#act-gas' )). toHaveText ( "Fee: 500 sats, from beta's balance, paid only if the holder accepts." );
await page . locator ( '#offer-item' ). selectOption ({ label : 'kit · held by acme' });
await page . locator ( '#offer-price' ). fill ( '2' );
await submit ( page , 'offer' );
await expect ( say ( page )). toHaveText ( 'beta offers 2 tBTC for kit. The offer stands until acme accepts it.' );
await expect ( page . locator ( '#clock-beat' )). toHaveText ( '4' ); // an offer is signatures, not a transaction
await expect ( inspect ). toContainText ( '2 tBTC for kit (open until acme accepts)' );
await hold ( 'acme' ). click ();
await expect ( inspect ). toContainText ( 'beta offers 2 tBTC' );
await inspect . getByRole ( 'button' , { name : "Accept beta's offer for kit" }). click ();
await expect ( say ( page )). toHaveText ( 'acme accepted: kit goes to beta for 2 tBTC, at beat 5.' );
await expect ( inspect ). toContainText ( 'kit · A starter kit. (now held by beta)' );
await page . locator ( '#timeline-list .ev' , { hasText : 'kit acme → beta, for 2 tBTC' }). click ();
for ( const text of [ 'Price' , '2 tBTC' , "beta's SegWit key (SIGHASH_ALL) when it made the offer, then acme's Taproot key when it accepted" ]) {
await expect ( inspect ). toContainText ( text );
}
await page . locator ( '[data-rule="none"]' ). click ();
await expect ( say ( page )). toHaveText ( 'Mainnet: no holding mines. Beats pay the lab wallet, and you fund holdings from it.' );
await expect ( page . locator ( '#clock-pays' )). toHaveText ( 'beats pay the lab wallet' );
await page . locator ( '#fund-to' ). selectOption ({ label : 'beta' });
await page . locator ( '#fund-amount' ). fill ( '3' );
await page . locator ( '#f-fund [type="submit"]' ). click ();
await expect ( say ( page )). toHaveText ( 'The lab wallet funded beta with 3 tBTC, at beat 6.' );
await hold ( 'beta' ). click ();
await expect ( page . locator ( '#timeline-list' )). toContainText ( '3 tBTC lab wallet → beta' );
await expect ( page . locator ( '#holdings-list' )). not . toContainText ( 'of each beat' );
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
expect ( problems ). toEqual ([]);
});
2026-10-15 13:32:29 -07:00
test ( 'the sample ends on both sides of a deal: sol accepts an offer, and lineage takes an ask for an org' , async ({ page }) => {
2026-10-15 00:32:29 -07:00
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' );
await page . locator ( '#sample' ). click ();
await expect ( say ( page )). toContainText ( 'Loaded the yolopreneur sample' , { timeout : 20_000 });
await row ( page , 'lineage' ). click ();
await expect ( inspect ). toContainText ( '0.1 tBTC for launch-kit (open until sol accepts)' );
await row ( page , 'sol' ). click ();
await expect ( inspect ). toContainText ( 'launch-kit · The page, the post and the checklist every venture launches with.' );
await inspect . getByRole ( 'button' , { name : "Accept lineage's offer for launch-kit" }). click ();
await expect ( say ( page )). toHaveText ( 'sol accepted: launch-kit goes to lineage for 0.1 tBTC, at beat 8.' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'launch-kit sol → lineage, for 0.1 tBTC' );
await row ( page , 'lineage' ). click ();
await expect ( inspect ). toContainText ( 'launch-kit · The page, the post and the checklist every venture launches with. (made by sol at beat 6)' );
2026-10-15 13:32:29 -07:00
await expect ( row ( page , 'mailr' )). toContainText ( 'for sale' );
await verb ( page , 'offer' );
await page . locator ( '#offer-item' ). selectOption ({ label : 'mailr · an org of well-known · asks 1 tBTC' });
await expect ( page . locator ( '#offer-ask-line' )). toHaveText ( "well-known asks 1 tBTC for mailr. Taking it costs the price, two 500-sat fees and a 600-sat coin of lineage's that leads the deal, and gas to adopt mailr at once." );
await page . locator ( '#offer-take' ). click ();
await expect ( say ( page )). toHaveText ( "lineage bought mailr from well-known for 1 tBTC and adopted it, at beat 9: its keys derive from lineage's seed now." );
await expect ( page . locator ( '#tree-sub' )). toHaveText ( 'lineage · now' );
await expect ( inspect ). toContainText ( 'from well-known to lineage, beat 9' );
await expect ( inspect ). toContainText ( 'derived: lineage holds its seed' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'join mailr to lineage, from well-known' );
await page . locator ( '#timeline-list .ev' , { hasText : 'mailr yolopreneur / well-known → lineage, for 1 tBTC' }). click ();
await expect ( inspect ). toContainText ( "the lot's Taproot key (SIGHASH_SINGLE|ANYONECANPAY) when well-known listed it, then lineage's SegWit key (SIGHASH_ALL) when it took it" );
await page . locator ( '#holdings-list .hold' , { has : page . locator ( '.hn' , { hasText : /^yolopreneur$/ }) }). click ();
await expect ( row ( page , 'mailr' )). toContainText ( 'to lineage' );
2026-10-15 00:32:29 -07:00
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
expect ( problems ). toEqual ([]);
});
2026-10-15 00:44:59 -07:00
2026-10-15 13:32:29 -07:00
test ( 'an org sells cash-free: its parent lists it, it is frozen, and another holding takes it and adopts it' , async ({ page }) => {
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' ), why = page . locator ( '#act-why' );
const hold = name => page . locator ( '#holdings-list .hold' , { has : page . locator ( '.hn' , { hasText : new RegExp ( `^ ${ name } $` ) }) });
await found ( page , 'acme' );
await expect ( say ( page )). toHaveText ( 'acme is founded, at beat 2.' );
await found ( page , 'beta' );
await expect ( say ( page )). toHaveText ( 'beta is founded, at beat 3.' );
await hold ( 'acme' ). click ();
await birth ( page , 'ops' );
await expect ( say ( page )). toHaveText ( 'ops is born to acme, at beat 4.' );
await verb ( page , 'sell' );
await page . locator ( '#sell-lot' ). selectOption ({ label : "ops · org 0'" });
2026-10-15 14:51:52 -07:00
await expect ( page . locator ( '#sell-note' )). toContainText ( "Cash-free: acme takes home the money in ops's tree first, every gas tank included, and its items go with it." );
2026-10-15 13:32:29 -07:00
await expect ( page . locator ( '#act-key' )). toContainText ( 'Signs as ops:' );
await page . locator ( '#sell-price' ). fill ( '3' );
await submit ( page , 'sell' );
2026-10-15 14:51:52 -07:00
await expect ( say ( page )). toHaveText ( "acme lists ops at 3 tBTC: any node can take it. The money in its tree went home to acme first, its items go with it, and it's frozen until the ask is taken or withdrawn." );
2026-10-15 13:32:29 -07:00
await expect ( inspect ). toContainText ( '3 tBTC for ops (open: any node can take it)' );
await row ( page , 'ops' ). click ();
await expect ( inspect ). toContainText ( '3 tBTC, asked by acme' );
await verb ( page , 'decide' );
await expect ( why ). toHaveText ( 'ops is listed for sale at 3 tBTC. Withdraw its ask first.' );
await hold ( 'beta' ). click ();
await verb ( page , 'offer' );
await page . locator ( '#offer-item' ). selectOption ({ label : 'ops · an org of acme · asks 3 tBTC' });
await page . locator ( '#offer-take' ). click ();
await expect ( say ( page )). toHaveText ( "beta bought ops from acme for 3 tBTC and adopted it, at beat 5: its keys derive from beta's seed now." );
await expect ( row ( page , 'ops' )). toBeVisible ();
await expect ( inspect ). toContainText ( 'from acme to beta, beat 5' );
await expect ( inspect ). toContainText ( "beta root › 0'" );
await hold ( 'acme' ). click ();
await expect ( row ( page , 'ops' )). toContainText ( 'to beta' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'ops acme → beta, for 3 tBTC' );
expect ( problems ). toEqual ([]);
});
test ( 'an offer on an org: its parent accepts it cash-free, and the buyer adopts it from Inspect' , async ({ page }) => {
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' );
const hold = name => page . locator ( '#holdings-list .hold' , { has : page . locator ( '.hn' , { hasText : new RegExp ( `^ ${ name } $` ) }) });
await found ( page , 'acme' );
await expect ( say ( page )). toHaveText ( 'acme is founded, at beat 2.' );
await found ( page , 'beta' );
await expect ( say ( page )). toHaveText ( 'beta is founded, at beat 3.' );
await hold ( 'acme' ). click ();
await birth ( page , 'ops' );
await expect ( say ( page )). toHaveText ( 'ops is born to acme, at beat 4.' );
await hold ( 'beta' ). click ();
await verb ( page , 'offer' );
await page . locator ( '#offer-item' ). selectOption ({ label : 'ops · an org of acme' });
await expect ( page . locator ( '#offer-ask' )). toBeHidden ();
await page . locator ( '#offer-price' ). fill ( '2' );
await submit ( page , 'offer' );
await expect ( say ( page )). toHaveText ( 'beta offers 2 tBTC for ops. The offer stands until acme accepts it.' );
await hold ( 'acme' ). click ();
await expect ( inspect ). toContainText ( 'ops beta offers 2 tBTC' );
await inspect . getByRole ( 'button' , { name : "Accept beta's offer for ops" }). click ();
2026-10-15 14:51:52 -07:00
await expect ( say ( page )). toHaveText ( 'acme accepted: ops goes to beta for 2 tBTC, at beat 5. The money in its tree went home to acme first, its items went with it, and beta adopts it next.' );
2026-10-15 13:32:29 -07:00
await expect ( row ( page , 'ops' )). toContainText ( 'sold' );
await row ( page , 'ops' ). click ();
await expect ( inspect ). toContainText ( 'to beta, which adopts it next' );
await verb ( page , 'decide' );
await expect ( page . locator ( '#act-why' )). toHaveText ( 'ops was sold to beta, which adopts it next.' );
await inspect . getByRole ( 'button' , { name : 'Adopt into beta' }). click ();
await expect ( say ( page )). toHaveText ( "beta adopted ops, at beat 6: its keys derive from beta's seed now." );
await expect ( row ( page , 'ops' )). toContainText ( 'to beta' );
expect ( problems ). toEqual ([]);
});
2026-10-15 14:51:52 -07:00
test ( 'a holding sells whole: its owner lists it with its tree, another holding takes it, and its history comes along' , async ({ page }) => {
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' ), why = page . locator ( '#act-why' );
const hold = name => page . locator ( '#holdings-list .hold' , { has : page . locator ( '.hn' , { hasText : new RegExp ( `^ ${ name } $` ) }) });
await found ( page , 'acme' );
await expect ( say ( page )). toHaveText ( 'acme is founded, at beat 2.' );
await found ( page , 'beta' );
await expect ( say ( page )). toHaveText ( 'beta is founded, at beat 3.' );
await hold ( 'acme' ). click ();
await birth ( page , 'ops' );
await expect ( say ( page )). toHaveText ( 'ops is born to acme, at beat 4.' );
await row ( page , 'acme' ). click ();
await verb ( page , 'sell' );
await page . locator ( '#sell-lot' ). selectOption ({ label : 'acme · the whole holding, with 1 below it' });
await expect ( page . locator ( '#sell-note' )). toContainText ( "Cash-free: its owner, the lab wallet, takes home the money in acme's tree first" );
await expect ( page . locator ( '#act-gas' )). toHaveText ( "Bundling its tree costs one 500-sat fee, from the lab wallet; whoever takes the ask pays the deal's fees." );
await page . locator ( '#sell-price' ). fill ( '5' );
await submit ( page , 'sell' );
await expect ( say ( page )). toHaveText ( "acme's owner lists it whole at 5 tBTC: any node can take it. The money in its tree went to the lab wallet first, its items go with it, and it's frozen until the ask is taken or withdrawn." );
await expect ( row ( page , 'acme' )). toContainText ( 'for sale' );
await expect ( inspect ). toContainText ( '5 tBTC, asked by its owner, the lab wallet' );
await expect ( hold ( 'acme' )). toContainText ( 'not mining' );
await row ( page , 'ops' ). click ();
await expect ( inspect ). toContainText ( "with acme's tree" );
await verb ( page , 'decide' );
await expect ( why ). toHaveText ( 'acme is listed for sale at 5 tBTC. Withdraw its ask first.' );
await hold ( 'beta' ). click ();
await verb ( page , 'offer' );
await page . locator ( '#offer-item' ). selectOption ({ label : 'acme · a holding, with 1 below it · asks 5 tBTC' });
await expect ( page . locator ( '#offer-ask-line' )). toHaveText ( "acme's owner asks 5 tBTC for acme, whole. Taking it costs the price, two 500-sat fees and a 600-sat coin of beta's that leads the deal, and gas to adopt acme at once." );
await page . locator ( '#offer-take' ). click ();
await expect ( say ( page )). toHaveText ( "beta bought acme whole for 5 tBTC and adopted it, at beat 6: its keys derive from beta's seed now." );
await expect ( page . locator ( '#holdings-list .hn' )). toHaveText ([ 'beta' ]);
await expect ( inspect ). toContainText ( 'whole, from its owner, to beta, beat 6' );
await expect ( inspect ). toContainText ( "beta root › 0'" );
await expect ( row ( page , 'ops' )). toBeVisible ();
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'found acme' );
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'join acme to beta, a holding till now' );
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
expect ( problems ). toEqual ([]);
});
2026-10-15 00:44:59 -07:00
test ( 'an agent pays a registry in another holding, and the registry awards it a belt' , async ({ page }) => {
const problems = await open ( page );
const hold = name => page . locator ( '#holdings-list .hold' , { has : page . locator ( '.hn' , { hasText : new RegExp ( `^ ${ name } $` ) }) });
await found ( page , 'well-known' );
await expect ( say ( page )). toHaveText ( 'well-known is founded, at beat 2.' );
await found ( page , 'acme' );
await expect ( say ( page )). toHaveText ( 'acme is founded, at beat 3.' );
await birth ( page , 'scout' , 'agent' );
await expect ( say ( page )). toHaveText ( 'scout is born to acme, at beat 4.' );
await pay ( page , 'acme / scout (agent)' , '1' );
await expect ( say ( page )). toHaveText ( 'acme sent 1 tBTC to scout, at beat 5.' );
await row ( page , 'scout' ). click ();
await pay ( page , 'well-known' , '0.1' );
await expect ( say ( page )). toHaveText ( 'scout sent 0.1 tBTC to well-known, at beat 6.' );
await hold ( 'well-known' ). click ();
await verb ( page , 'award' );
await page . locator ( '#award-to' ). selectOption ({ label : 'acme / scout (agent)' });
await page . locator ( '#award-skill' ). fill ( 'listen' );
await submit ( page , 'award' );
2026-10-15 15:48:31 -07:00
await expect ( say ( page )). toHaveText ( 'scout holds a white belt in listen, at beat 7, on its own sat.' );
2026-10-15 00:44:59 -07:00
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'award listen · white → scout, from well-known' );
await hold ( 'acme' ). click ();
await expect ( page . locator ( '#timeline-list' )). toContainText ( 'award listen · white → scout, from well-known' );
await expect ( row ( page , 'scout' )). toContainText ( 'listen' );
expect ( problems ). toEqual ([]);
});
2026-10-15 10:48:28 -07:00
test ( 'a node publishes its keyring; a graduation retires it, and the graduate publishes its new keys' , async ({ page }) => {
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' );
await page . locator ( '#sample' ). click ();
await expect ( say ( page )). toContainText ( 'Loaded the yolopreneur sample' , { timeout : 20_000 });
await row ( page , 'yolopreneur' ). click ();
await expect ( inspect ). toContainText ( 'published at beat 3' );
await expect ( inspect ). toContainText ( 'Its legal-control key, the one that holds the entity NFT' );
await row ( page , 'sol' ). click ();
await expect ( inspect ). toContainText ( 'published at beat 6' );
await expect ( inspect ). toContainText ( "account 2 of its org's seed, the way Ledger Live numbers Ethereum accounts" );
await row ( page , 'lineage' ). click ();
await expect ( inspect ). toContainText ( 'retired at beat 7' );
await expect ( inspect ). toContainText ( 'It graduated, so its keys changed with its new root. Keyring publishes a new one.' );
await verb ( page , 'keyring' );
await expect ( page . locator ( '#keyring-keys' )). toContainText ( 'npub1' );
await submit ( page , 'keyring' );
await expect ( say ( page )). toHaveText ( 'lineage published its keyring, at beat 8: four keys, each signing the same statement.' );
await expect ( inspect ). toContainText ( 'published at beat 8' );
await expect ( inspect . locator ( 'pre' , { hasText : 'lineage/v0 keyring' })). toContainText ( 'ethereum: 0x' );
await expect ( page . locator ( '#act-why' )). toHaveText ( "lineage's keyring already lists these keys, since beat 8." );
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
expect ( problems ). toEqual ([]);
});
2026-10-15 12:15:48 -07:00
test ( 'a holding is a series LLC with its ventures as series, and a seat graduates to go independent' , async ({ page }) => {
const problems = await open ( page ), inspect = page . locator ( '#inspect-body' );
await page . locator ( '#sample' ). click ();
await expect ( say ( page )). toContainText ( 'Loaded the yolopreneur sample' , { timeout : 20_000 });
await expect ( row ( page , 'yolopreneur' )). toContainText ( 'series llc' );
await row ( page , 'lineage' ). click ();
await expect ( inspect ). toContainText ( 'LLC · US-WY · Lineage LLC · via doola' );
await row ( page , 'docscope' ). click ();
await verb ( page , 'charter' );
await expect ( page . locator ( '#charter-forms input[value="series"]' )). toBeChecked ();
await expect ( page . locator ( '#act-key' )). toContainText ( 'Signs as yolopreneur' );
await submit ( page , 'charter' );
await expect ( say ( page )). toHaveText ( 'docscope is a series of yolopreneur, at beat 8.' );
await row ( page , 'yolopreneur' ). click ();
await expect ( inspect . locator ( '.f' , { has : page . locator ( 'dt' , { hasText : /^Series$/ }) })). toContainText ( 'well-known, time, docscope' );
await row ( page , 'sol' ). click ();
await verb ( page , 'graduate' );
await submit ( page , 'graduate' );
await expect ( say ( page )). toContainText ( 'sol graduates from time' );
await expect ( row ( page , 'sol' )). toContainText ( 'graduated' );
await verb ( page , 'charter' );
await page . locator ( '#charter-forms input[value="llc"]' ). check ();
await page . locator ( '#charter-jurisdiction' ). fill ( 'US-DE' );
await page . locator ( '#charter-legal' ). fill ( 'Sol LLC' );
await submit ( page , 'charter' );
await expect ( say ( page )). toContainText ( 'sol is chartered as an LLC in US-DE' );
await verb ( page , 'birth' );
await expect ( page . locator ( '#f-birth' )). toBeVisible ();
expect ( await page . evaluate (() => document . documentElement . scrollWidth <= innerWidth )). toBe ( true );
expect ( problems ). toEqual ([]);
});