cyberia/cyberia-my/.claude/plans/2026-10-14-leasehold-alpha.md

leasehold alpha — 2026-10-14

Goal (user): launch the alpha of the land product by the end of the week — cyberia.my/map + plot pages + the lease pack + graph pages. The user's critical list is the spine of this plan; items are ordered by dependency.

where things stand (from the transcripts of 09-25, 09-28, 10-12, 10-13)

  • map: 127 plots, prysm land use, lease book from the sheet, trails, friends' sales, no popups; /plot/:id = hero · LAND board · DEAL track (local) · DOCS (passport JSON, KML, uploads in localStorage).
  • pack: cve/legal/10 hak sewa/ — template ID/EN, akta simple, Annexes A–G, reading pack EN-RU (PDF). Public copies on cyberia.blog under cyber-valley/products/leasehold/ (template, simple, perjanjian, design code). Policies, referral system, rates, century index — all live. Open in the pack: bracket values (fees, terms, garden/pond/water norms), the KEPMEN number on HGB 00048 page 1, notary read of akta + agreement.
  • player: Atlas Shrugged set already on every page (cyberia.to/player.js, download at /music/atlas.shrugged.set.mp3). A new track swaps in when sent.
  • gaps against the user's list: every plot URL serves one generic ; robots.txt / sitemap.xml fall through to index.html; no survey / media per plot and no ORDER button; no Telegram flow; /me is a mock economy dashboard, not a client cabinet; no document library; reading-pack PDF link is dead for outsiders (cve went private 09-28); <code>company licences</code> and <code>office practice</code> pages exist only in private cve; no wallet connect; no NFT side of the plot page; nothing on IPFS.</li> </ul> <h2><a class="heading-anchor" id="task-pool"></a>task pool</h2> <ol> <li>SEO per plot — static <code>plot/<id>/index.html</code> with its own title, description (district · land use · size in ares · m² in plan as on the Google map · surveyed m² when known · price), canonical, og:*, JSON-LD; <code>sitemap.xml</code>, <code>robots.txt</code>; the same shell loads the SPA. Script <code>scripts/render-plot-pages.py</code>, called by <code>deploy.nu</code> after the build.</li> <li>survey + media on the plot page — <code>measured_m2</code>/<code>measured_on</code> already in the book; a SURVEY card (figure + date, or ORDER SURVEY) and a MEDIA card (photos / drone video, or ORDER PHOTO · DRONE). Media comes from <code>/plot-data/<id>.json</code> fetched at runtime so the bot can publish without a rebuild. ORDER = Telegram deep link <code>t.me/<bot>?start=<kind>_<plot></code>; until the bot runs, a DM to @st_joy with the text prefilled.</li> <li>client cabinet <code>/cabinet</code> — every status in one place: deal track, due diligence, KYC, wallet, survey, media, documents; orders log; support links (DM @st_joy, group). All state in the browser, nothing on our server.</li> <li>document library <code>/docs</code> — templates and the pack (graph links), rules pages, century index, rates; land + company documents for the deal as IPFS links (Pinata) once pinned; the reading-pack PDF pinned to IPFS.</li> <li>wallet connect — EIP-1193 (<code>eth_requestAccounts</code>), address shown in the cabinet and on the plot page; CYB neuron already exists (mudra key).</li> <li>NFT side of the plot page — <code>plot/<id>/metadata.json</code> (ERC-721 metadata: name, description, image, attributes) from the same renderer; ON-CHAIN card on the page (contract + token id once deployed). Contract and chain need the user's answer (chain, deployer, DAO address).</li> <li>hardforce bot — one process (Python std lib, long polling): <code>/start</code> payload → task posted to the hardforce group → media replied in the group → pinned to Pinata → <code>plot-data/<id>.json</code> pushed to the pages repo → live within a minute. Needs: bot token, group id, Pinata JWT.</li> <li>pack — pin the EN-RU reading pack; publish <code>company licences</code> and <code>office practice</code> to the public graph (company data is public by the user's rule); the bracket values stay the user's decisions.</li> </ol> <h2><a class="heading-anchor" id="needs-from-the-user"></a>needs from the user</h2> <ul> <li>Telegram: bot token (BotFather), the hardforce group (add the bot, send its id), the support handle to show (default @st_joy).</li> <li>Pinata JWT for pinning the pack and the deal documents.</li> <li>NFT: chain (Ethereum mainnet / Base / testnet), who holds the deployer key, the DAO address to own the collection.</li> <li>the music track to replace or join the Atlas Shrugged set.</li> <li>bracket values in the pack; the KEPMEN number from HGB 00048 page 1.</li> </ul> <h2><a class="heading-anchor" id="status"></a>status</h2> <ul> <li><input type="checkbox" checked="" disabled="" /> 1 seo - [x] 2 survey/media - [x] 3 cabinet - [x] 4 docs</li> <li><input type="checkbox" checked="" disabled="" /> 5 wallet - [x] 6 nft metadata (contract waits for the chain answer)</li> <li><input type="checkbox" checked="" disabled="" /> 7 bot written (<code>bot/</code>), runs once the token, group id and Pinata JWT exist</li> <li><input type="checkbox" checked="" disabled="" /> 8 pack — pinned, CIDs in <code>src/docs_library.json</code> (written by <code>cve/tools/publish.py</code>); company licences / office practice pages wait for the user's go</li> </ul> <p>Deployed to cyberia.my 2026-10-14 (commits f431f33…c32456d). Local check: <code>python3 scripts/serve-dist.py 8099</code> + <code>python3 scripts/cdp.py <url> --selectors <selectors…></code>. Open: the MOCK badge on every phase pill (<code>style.css</code> <code>.cyberia-phase-pill::after</code>) sits on the flat page of a real product — the user decides.</p> <h2><a class="heading-anchor" id="second-pass-2026-10-14-after-the-user-s-answers"></a>second pass 2026-10-14 (after the user's answers)</h2> <ul> <li><input type="checkbox" checked="" disabled="" /> pay — premium or upfront to the Company's Permata account (details from the bank letter), ETH rail waits for the Company's address, sent-vs-received tracking; the valley confirms by bot <code>/paid</code> → the flat paints IN TRANSFER (reserved) on the map, <code>/step signed</code> → OWNED</li> <li><input type="checkbox" checked="" disabled="" /> register — <code>plot-data/<id>.json</code> <code>deal</code> + <code>plot-data/index.json</code>, merged into the deals signal on map, plot page, cabinet; <code>/admin</code> for the valley and the notary (recitals, Annex C inventory, commands)</li> <li><input type="checkbox" checked="" disabled="" /> documents — 17 files on IPFS (company: NIB, NPWP, AHU 2025, akta 01/2022, 13 and 14/2025, AHU detail 2025-07, AoA 2026-08 ID + EN-ID; land: AJB 81/2023, PPJB, sale authorization, BPN consideration 2022, PKKPR, SPPT PBB 2023, BPHTB; the reading pack), personal IDs and addresses blacked out (<code>cve/tools/redact.py</code>), CIDs in <code>cve/legal/signing/ipfs cids.json</code></li> <li><input type="checkbox" checked="" disabled="" /> wallet guide in the cabinet when no provider is injected</li> <li><input type="checkbox" checked="" disabled="" /> HGB 00048 certificate (2026-10-21): the user dropped it; KEPMEN ATR/KBPN No. 1339/SK-HK.02/X/2022 confirmed on the BPN check page; birth dates blacked out (hand-placed boxes, <code>REDACT_BOXES</code>), wrapped, pinned, first in THE LAND. The «KKPR» file was the PKKPR already on the shelf (same text)</li> <li><input type="checkbox" checked="" disabled="" /> no 2026 deed exists — the Director's appointment is Akta No. 13/2025 + AHU-AH.01.09-0196845, both on the shelf; the «2026 deed» card was a placeholder of mine, removed 2026-10-21</li> <li><input type="checkbox" checked="" disabled="" /> ETH address 2026-10-22 — 0x15534f1875977148EBe5FFb8A3D51C9F996bD0ac on the ETH rail (EIP-55 casing verified by keccak); ETH or USDT on mainnet, reference to support after sending</li> <li><input type="checkbox" checked="" disabled="" /> bot 2026-10-21 — @cybervalley_hardforce_bot live on cyberproxy (<code>/opt/hardforce</code>, service <code>hardforce</code>, secrets in <code>/etc/hardforce.env</code>, runs as www-data, owns <code>/opt/hardforce</code> and <code>docroot/plot-data</code>); group «cyber valley» id -1002245058785; ORDER buttons deep-link <code>?start=survey_<plot>|media_<plot></code></li> <li><input type="checkbox" checked="" disabled="" /> ten more languages 2026-10-21 (de, fr, es, ja, ko, pt, it, tr, ar, hi) — every signable page and the bound agreement open in 14 second languages beside Indonesian; ja/ko/ar/hi set in Noto Sans (fetch per tools/README, not committed), Arabic column right-to-left; one ASK row left for any other language</li> <li><input type="checkbox" checked="" disabled="" /> bracket values settled 2026-10-22 — all rows closed; only the stay fee stands at [20]% by the user's «пусть стоит пока 20»</li> <li><input type="checkbox" checked="" disabled="" /> hash on a document page is a QR of its IPFS address (<code>qrcode</code> crate, svg) 2026-10-21</li> <li><input type="checkbox" checked="" disabled="" /> Annex B is a template: the sinwood-25 site plan left the pack 2026-10-21 («мы работаем с шаблонами»)</li> <li><input type="checkbox" checked="" disabled="" /> 3.5 Guarantee Fund outflows «Director and one Commissioner» stay as they are (the user, 2026-10-21)</li> <li><input type="checkbox" checked="" disabled="" /> languages 2026-10-21 — every signable page opens in ID, ID+EN, ID+RU, ID+UK, ID+ZH: agreement via tables <code>cve/legal/10 hak sewa/translations/hak sewa template.{ru,uk,zh}.json</code> (515 strings each), annexes as block-identical md in <code>cve/legal/annexes/{id,uk,zh}/</code> (RU in the reading pack); packs 81/80/85 pages; links on the graph pages and on the shelf; other languages stay ASK → generated on request the same way</li> <li><input type="checkbox" checked="" disabled="" /> languages 2026-10-20 — Bahasa Indonesia is the book; the choice lives on <code>/docs/<key></code>: OPEN where on the shelf (ID and ID+EN for the agreement and Annexes A–G, ID+RU for the annexes), ASK → Telegram DM for the rest, generated on request. Open: RU for the agreement</li> <li><input type="checkbox" checked="" disabled="" /> one instrument 2026-10-20 — the agreement is the akta; «akta sewa simple» and the notary's template retired from the graph, cve and the shelf; the pack = agreement + annexes</li> </ul> <h2><a class="heading-anchor" id="after-the-contracts-open-questions-the-user-2026-10-21-ost-voprosy-v-spisok-na-posle-dogovorov"></a>after the contracts — open questions (the user, 2026-10-21: «ост вопросы в список на после договоров»)</h2> <ol> <li>[done 2026-10-22] Bracket values — all settled; stay fee [20]% provisional by the user's word.</li> <li>Hash on the document cards — the QR sits on the document page; none on the cards. Keep so?</li> <li>[done 2026-10-22] the reading-translation note sits under every document's language list; native review stays open for later if ever wanted.</li> <li>Test the bot end to end with a real ORDER (survey / media) and a crew reply — the user: later.</li> <li>[done 2026-10-22] <code>legal/company licences.md</code> and <code>legal/office practice.md</code> live on the graph; the eight office-practice answers await the lawyers' thread (separate, later — the user).</li> <li>[done 2026-10-22] Provenance page — <code>products/leasehold/value provenance.md</code> on the graph.</li> <li>[done 2026-10-22] 11.3 settled: 10% + «while a district counts fewer than ten Holders, the Board answers any Holder».</li> <li>[done 2026-10-22] 12.1.9 settled at 2.0%.</li> </ol> </div> <nav class="page-tabs" aria-label="Page sections"> <a class="page-tab" data-target="neighbours" href="#neighbours">Graph</a> </nav> <section id="neighbours" class="page-minimap"> <h2>Graph</h2> <div id="minimap-container" data-page-id="cyberia/cyberia-my/.claude/plans/2026-10-14-leasehold-alpha" data-depth="2" data-max-nodes="30" ></div> </section> <script src="/static/graph.js"></script> <script> (function () { var tabs = Array.prototype.slice.call(document.querySelectorAll('.page-tab')); var siteHeader = document.querySelector('.site-header'); var titleBar = document.querySelector('.page-title-bar'); var tabsBar = document.querySelector('.page-tabs'); // Site-header was sticky-top; stacked-sticky offsets used its // height to anchor title-bar and tabs-bar below it. Now the // header lives at the bottom — title-bar should pin to top: 0 // directly. Returning 0 keeps the var-driven stacking math // valid without touching every selector that reads // --site-header-h or --sticky-h. function siteHeaderHeight() { return 0; } function titleBarHeight() { return titleBar ? titleBar.offsetHeight : 0; } function tabsBarHeight() { return tabsBar ? tabsBar.offsetHeight : 0; } function syncOffsets() { var sh = siteHeaderHeight(); var th = titleBarHeight(); var bh = tabsBarHeight(); document.documentElement.style.setProperty('--site-header-h', sh + 'px'); document.documentElement.style.setProperty('--title-bar-h', th + 'px'); document.documentElement.style.setProperty('--sticky-h', (sh + th) + 'px'); document.documentElement.style.setProperty('--sticky-h-full', (sh + th + bh) + 'px'); } syncOffsets(); window.addEventListener('resize', syncOffsets, { passive: true }); if (!tabs.length) return; var sections = tabs .map(function (t) { return document.getElementById(t.dataset.target); }) .filter(Boolean); if (!sections.length) return; tabs.forEach(function (t) { t.addEventListener('click', function (e) { var el = document.getElementById(t.dataset.target); if (!el) return; e.preventDefault(); var offset = siteHeaderHeight() + titleBarHeight() + tabsBarHeight() + 12; var top = el.getBoundingClientRect().top + window.scrollY - offset; window.scrollTo({ top: top, behavior: 'smooth' }); history.replaceState(null, '', '#' + t.dataset.target); }); }); function update() { var threshold = siteHeaderHeight() + titleBarHeight() + tabsBarHeight() + 24; var active = -1; sections.forEach(function (s, i) { if (s.getBoundingClientRect().top - threshold <= 0) active = i; }); tabs.forEach(function (t, i) { t.classList.toggle('is-active', i === active); }); } window.addEventListener('scroll', update, { passive: true }); window.addEventListener('resize', update, { passive: true }); update(); })(); </script> </article> </main> </div> <script src="/static/search.js" defer></script> <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" ></script> <script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/auto-render.min.js" ></script> <script> document.addEventListener( "DOMContentLoaded", function () { // Unescape HTML entities inside math delimiters before KaTeX processes them // comrak escapes > to >, < to <, etc. which breaks LaTeX var content = document.querySelector( ".main-content", ); if (!content) return; var walker = document.createTreeWalker( content, NodeFilter.SHOW_TEXT, null, false, ); while (walker.nextNode()) { var node = walker.currentNode; if ( node.nodeValue && /\$/.test(node.nodeValue) ) { var text = node.nodeValue; // Fix HTML entities inside $...$ and $$...$$ text = text.replace( /\$\$([^$]+)\$\$/g, function (m, inner) { return ( "$$" + inner .replace( />/g, ">", ) .replace( /</g, "<", ) .replace( /&/g, "&", ) + "$$" ); }, ); text = text.replace( /\$([^$]+)\$/g, function (m, inner) { return ( "$" + inner .replace( />/g, ">", ) .replace( /</g, "<", ) .replace( /&/g, "&", ) + "$" ); }, ); if (text !== node.nodeValue) { node.nodeValue = text; } } } // Now render math if ( typeof renderMathInElement === "function" ) { renderMathInElement(content, { delimiters: [ { left: "$$", right: "$$", display: true, }, { left: "$", right: "$", display: false, }, ], ignoredTags: [ "script", "noscript", "style", "textarea", "pre", "code", "a", ], throwOnError: false, }); } }, ); </script> <!-- Hover-prefetch removed. It was causing rapid-click stalls: <link rel=prefetch> kicks off a fetch, and removing the <link> element doesn't reliably abort the in-flight request in Chromium — the fetch keeps consuming an HTTP/1.1 connection slot until it completes. Multiple rapid hovers saturate the 6-per-origin cap, queueing the next navigation behind them. The browser already does its own speculation rules; the cost of a single round-trip on click is negligible compared to the stall this script was producing. --> </body> </html>