Skip to content

Commit 49b9750

Browse files
committed
fix(honesty+mobile): round-14 — mark demo lanes on /embed /og /ics (deliveries+milestone no longer leak as real on-chain), exclude demo from feed/status/app active counts, wrap app tabs so Activity stays reachable on mobile
1 parent 40ce0c5 commit 49b9750

5 files changed

Lines changed: 24 additions & 11 deletions

File tree

matcher/src/api.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export interface ApiConfig {
7070
getSubscription?: (id: number) => {
7171
id: number; owner: string; webhook_url: string;
7272
balance: string; deliveries: number; active: boolean; created_at: number;
73+
demo?: boolean;
7374
} | null;
7475
/** Optional delivery-rate calc for /sub/:id.ics balance-runout projection. */
7576
getDeliveryRate?: (id: number) => { count: number; window_seconds: number; per_day: number };
@@ -356,6 +357,7 @@ interface BadgeSpec { label: string; value: string; color: string }
356357
*/
357358
function renderEmbed(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscription']>>>, contractHash: string, chain: string): string {
358359
const cspr = (BigInt(sub.balance) / 1_000_000_000n).toString();
360+
const demo = sub.demo === true;
359361
const explorer = chain === 'casper-test' ? 'https://testnet.cspr.live' : 'https://cspr.live';
360362
const esc = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
361363
return `<!DOCTYPE html>
@@ -376,7 +378,7 @@ function renderEmbed(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
376378
.num{font:600 28px/1 'Casper Sans',Inter,sans-serif;letter-spacing:-.02em}
377379
.small{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:#888}
378380
.pill{padding:2px 8px;font:600 9px 'JetBrains Mono';letter-spacing:.08em;border-radius:2px}
379-
.ok{background:#3edc64;color:#000}.bad{background:#ff2d2e;color:#fff}
381+
.ok{background:#3edc64;color:#000}.bad{background:#ff2d2e;color:#fff}.demo{background:#ffb347;color:#000}
380382
</style>
381383
</head><body>
382384
<a class="card" id="card" href="${explorer}/contract/${contractHash}" target="_blank" rel="noopener" title="Open Sluice subscription #${sub.id} on cspr.live">
@@ -386,6 +388,7 @@ function renderEmbed(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
386388
<span class="mono small">SUB</span>
387389
<span class="mono" style="font-weight:600">#${sub.id}</span>
388390
<span style="flex:1"></span>
391+
${demo ? '<span class="pill demo" title="Simulated demo lane — no real on-chain escrow">DEMO</span>' : ''}
389392
<span class="pill ${sub.active ? 'ok' : 'bad'}" id="status">${sub.active ? 'ACTIVE' : 'INACTIVE'}</span>
390393
</div>
391394
<div class="row" style="gap:24px">
@@ -402,6 +405,7 @@ function renderEmbed(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
402405
<span>${chain.toUpperCase()}</span>
403406
<span style="color:#444">·</span>
404407
<span>updated <span id="ago">just now</span></span>
408+
${demo ? '<span style="color:#444">·</span><span style="color:#ffb347">demo · simulated</span>' : ''}
405409
<span style="flex:1"></span>
406410
<span class="accent">sluice.unitynodes.com ↗</span>
407411
</div>
@@ -503,6 +507,7 @@ function renderOgSvg(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
503507
const status = sub.active ? 'ACTIVE' : 'INACTIVE';
504508
const statusBg = sub.active ? '#3edc64' : '#ff2d2e';
505509
const chainBadge = chain.toUpperCase();
510+
const demo = sub.demo === true;
506511
const esc = (s: string) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
507512
return `<?xml version="1.0" encoding="UTF-8"?>
508513
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" width="1200" height="630" role="img" aria-label="Sluice subscription ${sub.id}">
@@ -530,6 +535,10 @@ function renderOgSvg(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubsc
530535
<rect width="100" height="32" fill="${statusBg}"/>
531536
<text x="50" y="22" font-size="14" font-weight="600" fill="#000" text-anchor="middle" letter-spacing="2">${status}</text>
532537
</g>
538+
${demo ? `<g transform="translate(172, 380)">
539+
<rect width="160" height="32" fill="#ffb347"/>
540+
<text x="80" y="22" font-size="14" font-weight="600" fill="#000" text-anchor="middle" letter-spacing="2">DEMO · SIMULATED</text>
541+
</g>` : ''}
533542
<g transform="translate(60, 470)">
534543
<text font-size="14" font-weight="500" fill="#666" letter-spacing="2">BALANCE</text>
535544
<text y="32" font-size="32" font-weight="500" fill="#fff">${esc(cspr)} CSPR</text>
@@ -577,6 +586,7 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
577586
// Float division: integer BigInt division truncated sub-1-CSPR balances to 0,
578587
// which skipped the runway reminder for exactly the subs that need it.
579588
const balanceCspr = Number(sub.balance) / motesPerCspr;
589+
const demo = sub.demo === true;
580590
const perDeliveryMotes = Number(process.env.SLUICE_PER_DELIVERY_MOTES ?? 500_000_000); // 0.5 CSPR
581591
const perDeliveryCspr = perDeliveryMotes / motesPerCspr;
582592
const explorer = chain === 'casper-test' ? 'https://testnet.cspr.live' : 'https://cspr.live';
@@ -587,8 +597,10 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
587597
lines.push('PRODID:-//Sluice//Subscription Calendar//EN');
588598
lines.push('CALSCALE:GREGORIAN');
589599
lines.push('METHOD:PUBLISH');
590-
lines.push(`X-WR-CALNAME:Sluice sub_${sub.id}`);
591-
lines.push(`X-WR-CALDESC:On-chain event subscription on Casper (${chain})`);
600+
lines.push(`X-WR-CALNAME:Sluice sub_${sub.id}${demo ? ' (demo)' : ''}`);
601+
lines.push(`X-WR-CALDESC:${demo
602+
? `Demo lane on Casper (${chain}) — simulated deliveries, no real on-chain escrow`
603+
: `On-chain event subscription on Casper (${chain})`}`);
592604

593605
// Balance-runs-out projection, only if we have a measurable rate.
594606
if (rate.per_day > 0 && perDeliveryCspr > 0) {
@@ -625,8 +637,9 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
625637
lines.push(`SUMMARY:${icsEscape(`Sluice sub_${sub.id}, weekly check-in`)}`);
626638
lines.push(`DESCRIPTION:${icsEscape(
627639
`Quick weekly look at subscription #${sub.id}:\n` +
640+
(demo ? `• demo lane: simulated deliveries, no real on-chain escrow\n` : '') +
628641
`• balance: ${balanceCspr} CSPR\n` +
629-
`• deliveries to date: ${sub.deliveries}\n` +
642+
`• deliveries to date: ${sub.deliveries}${demo ? ' (simulated)' : ''}\n` +
630643
`• webhook: ${sub.webhook_url}\n` +
631644
`Open the dashboard: https://sluice.unitynodes.com/app`
632645
)}`);
@@ -635,7 +648,7 @@ function buildIcs(sub: NonNullable<ReturnType<NonNullable<ApiConfig['getSubscrip
635648

636649
// Delivery-count milestones already crossed, fixed dates in the past so
637650
// calendar apps surface them as a one-tap history of the subscription.
638-
for (const milestone of [100, 500, 1000, 5000, 10000]) {
651+
for (const milestone of demo ? [] : [100, 500, 1000, 5000, 10000]) {
639652
if (sub.deliveries < milestone) continue;
640653
const at = new Date(sub.created_at * 1000); // approximate, we don't track exact crossing
641654
lines.push('BEGIN:VEVENT');

web/app/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@
282282
function renderHeader() {
283283
const s = state.snapshot;
284284
const subs = s.subscriptions || [];
285-
const active = subs.filter(x => x.active).length;
285+
const active = subs.filter(x => x.active && !x.demo).length;
286286
const mine = state.wallet.connected
287287
? subs.filter(x => x.owner === state.wallet.accountHash).length
288288
: null;

web/app/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@
6464
/* main padding */
6565
body > div > div[style*="max-width:1440px"] { padding: 20px 14px 40px !important; }
6666
h1[style*="font:500 44px"] { font-size: 30px !important; }
67-
/* tabs scroll horizontally */
68-
.app-tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; }
69-
.app-tab { padding: 12px 16px !important; font-size: 13px !important; white-space: nowrap; }
67+
/* tabs wrap so every tab stays reachable */
68+
.app-tabs { flex-wrap: wrap; overflow-x: auto; -webkit-overflow-scrolling: touch; }
69+
.app-tab { flex: 1 1 auto; justify-content: center; padding: 12px 14px !important; font-size: 13px !important; white-space: nowrap; border-bottom: 1px solid #000; }
7070
/* stats strip 4 → 2x2 */
7171
[style*="grid-template-columns:repeat(4,1fr)"] { grid-template-columns: repeat(2,1fr) !important; }
7272
[style*="grid-template-columns:repeat(4,1fr)"] [style*="font:500 40px"] { font-size: 26px !important; }

web/feed/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ <h1 style="margin:14px 0 0;font:500 44px/1.05 'Casper Sans',Inter;letter-spacing
163163
$('ls-contract-events').href = `https://testnet.cspr.live/contract-package/${snap.contract_hash}?tab=events`;
164164
$('ls-total').textContent = subs.filter(s => !s.demo).reduce((a, s) => a + (s.deliveries || 0), 0).toLocaleString();
165165
$('ls-showing').textContent = events.length;
166-
$('ls-active').textContent = subs.filter(s => s.active).length;
166+
$('ls-active').textContent = subs.filter(s => s.active && !s.demo).length;
167167
const ok = events.filter(e => e.status >= 200 && e.status < 300).length;
168168
$('ls-success').textContent = events.length ? `${((ok / events.length) * 100).toFixed(0)}%` : '…';
169169
const lats = events.map(e => e.latency_ms).filter(l => Number.isFinite(l) && l > 0);

web/status/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ <h1 id="overall-text" style="margin:0;font:500 clamp(28px,3vw,40px)/1.1 'Casper
260260

261261
setOverall([api.status, snap.status, metrics.status, chain.status, wsStatus]);
262262

263-
$('stat-subs').textContent = snap.data ? snap.data.subscriptions.filter((s) => s.active).length : '…';
263+
$('stat-subs').textContent = snap.data ? snap.data.subscriptions.filter((s) => s.active && !s.demo).length : '…';
264264
$('stat-deliveries').textContent = snap.data ? num(snap.data.subscriptions.filter(s => !s.demo).reduce((a, s) => a + (s.deliveries || 0), 0)) : '…';
265265
if (metrics.body) {
266266
const ok = parseMetric(metrics.body, 'sluice_webhook_results_total{result="ok"}') ?? 0;

0 commit comments

Comments
 (0)