Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
c94d3c7
v42.4.1
kojibai Jan 27, 2026
a1c28c8
v42.4.2
kojibai Jan 27, 2026
f75d295
v42.4.3
kojibai Jan 27, 2026
41d0171
Prevent KaiVoh pull-to-refresh during input focus
kojibai Jan 30, 2026
5bc91c5
Disable pull-to-refresh in KaiVoh modal
kojibai Jan 30, 2026
a73da7a
Merge pull request #314 from kojibai/codex/debug-kai-voh-feature-relo…
kojibai Jan 30, 2026
45770e4
v42.4.4
kojibai Jan 30, 2026
d5bd9a8
Block reloads while KaiVoh modal is open
kojibai Jan 30, 2026
1c7a894
Fix KaiVoh reload guard typing
kojibai Jan 30, 2026
48a0c1b
Merge pull request #317 from kojibai/codex/fix-kaivoh-refresh-issue-i…
kojibai Jan 30, 2026
b953ab4
v42.4.5
kojibai Jan 30, 2026
edcb6bb
Add Kaivoh SSR rewrites
kojibai Jan 30, 2026
bf15513
Merge pull request #318 from kojibai/codex/fix-not_found-error-on-kai…
kojibai Jan 30, 2026
4d16d30
v42.4.6
kojibai Jan 30, 2026
24f7b67
v42.4.7
kojibai Jan 30, 2026
1d4b960
v42.4.8
kojibai Jan 30, 2026
990b418
Route sigils API through same-origin proxy
kojibai Jan 30, 2026
319efa0
Merge pull request #319 from kojibai/codex/fix-cors-errors-in-kaivoh
kojibai Jan 30, 2026
b2ac2c2
Fix sigil API fallback on non-API domains
kojibai Jan 30, 2026
429d860
Merge pull request #320 from kojibai/codex/investigate-404-error-for-…
kojibai Jan 30, 2026
766239e
Restore production API base selection
kojibai Jan 30, 2026
410fb4b
Merge pull request #321 from kojibai/codex/restore-production-api-bas…
kojibai Jan 30, 2026
b419cdc
Fix API base selection to prefer same-origin proxy
kojibai Jan 30, 2026
1d404a2
Merge pull request #322 from kojibai/codex/fix-cors-issues-for-api-ac…
kojibai Jan 30, 2026
376a135
v42.4.9
kojibai Jan 30, 2026
c7890b1
v42.5.0
kojibai Jan 30, 2026
c914170
Fix SigilExplorer API URL construction
kojibai Jan 30, 2026
ae25e66
Gate same-origin SigilExplorer proxy to dev or opt-in
kojibai Jan 30, 2026
8891e3c
Merge pull request #324 from kojibai/codex/investigate-invalid-base-u…
kojibai Jan 30, 2026
bd2496d
Limit inhale batch payload size
kojibai Jan 30, 2026
4451d2e
Merge pull request #325 from kojibai/codex/investigate-413-request-en…
kojibai Jan 30, 2026
4cff8a4
v42.5.1
kojibai Jan 30, 2026
ef058d3
v42.5.2
kojibai Jan 30, 2026
61a0eed
v42.6.0
kojibai Jan 30, 2026
e6e9b53
v42.6.1
kojibai Jan 30, 2026
b0d7c02
Add proof bundle metadata to ExhaleNote exports
kojibai Jan 30, 2026
31ecc5c
Fix ExhaleNote QR to use verifier URL
kojibai Jan 30, 2026
a53fcc3
v42.6.2
kojibai Jan 30, 2026
5bbdd8d
Merge pull request #327 from kojibai/codex/update-exhale-note-for-ver…
kojibai Jan 30, 2026
9c08cd5
Add USD value to exhale note
kojibai Jan 30, 2026
4d9ceab
v42.6.3
kojibai Jan 30, 2026
9e55283
Merge pull request #328 from kojibai/codex/add-usd-value-to-exhale-note
kojibai Jan 30, 2026
c3d8134
Add custom exhale note sends and receipt metadata
kojibai Jan 31, 2026
4651f15
Add custom exhale note sends and receipt metadata
kojibai Jan 31, 2026
38659e2
Add custom exhale note sends and receipt metadata
kojibai Jan 31, 2026
2c85c68
Add custom exhale note sends and receipt metadata
kojibai Jan 31, 2026
c32cc0c
Reset send reservation on amount edits
kojibai Jan 31, 2026
bd3175b
Merge pull request #330 from kojibai/codex/fix-re-export-to-handle-se…
kojibai Jan 31, 2026
8299b4b
Merge pull request #329 from kojibai/codex/add-custom-amount-flow-for…
kojibai Jan 31, 2026
90c9f62
Fix verify URLs in note PDFs
kojibai Jan 31, 2026
9a63aa5
Derive full verify URL for note QR
kojibai Jan 31, 2026
317e0ce
v42.6.4
kojibai Jan 31, 2026
c8a8410
Merge pull request #331 from kojibai/codex/fix-pdf-rendering-and-veri…
kojibai Jan 31, 2026
19ca5b5
Fix PDF parsing for large proof bundles
kojibai Jan 31, 2026
a9daa3f
Merge pull request #332 from kojibai/codex/handle-large-proof-bundles…
kojibai Jan 31, 2026
2386922
Update exhale note proof output and verify status
kojibai Jan 31, 2026
f17a251
Populate note sigil fields on render lock
kojibai Jan 31, 2026
b112fea
Restore note cover page in PDF
kojibai Jan 31, 2026
f935892
Lock verified pulse and receipt hash on render
kojibai Jan 31, 2026
44f5d8f
Refresh note claim status with ledger updates
kojibai Jan 31, 2026
b48141c
Merge pull request #333 from kojibai/codex/update-pdf-generation-for-…
kojibai Jan 31, 2026
9c7dc81
Mark note claims on SVG download
kojibai Jan 31, 2026
89199a7
Merge pull request #334 from kojibai/codex/implement-unclaim-to-claim…
kojibai Jan 31, 2026
d120624
Update verify note download to png
kojibai Jan 31, 2026
fca681c
Persist note claim status in sigil registry
kojibai Jan 31, 2026
21baf33
Sync note claims from remote registry
kojibai Jan 31, 2026
50dbc25
Broadcast note claims to global registry
kojibai Jan 31, 2026
f19bd13
Allow re-claimable note export
kojibai Jan 31, 2026
173c6c2
Provide base fields for note claim payload
kojibai Jan 31, 2026
3362fec
v42.6.5
kojibai Jan 31, 2026
51520ea
Merge pull request #335 from kojibai/codex/update-exhale-note-to-png-…
kojibai Jan 31, 2026
86a3f7a
v42.6.6
kojibai Jan 31, 2026
8e950e2
Fix exhale note QR verify link
kojibai Jan 31, 2026
3f6cf67
Merge pull request #336 from kojibai/codex/fix-qr-code-scanning-issue
kojibai Jan 31, 2026
eb7f8a7
v42.6.7
kojibai Jan 31, 2026
342ee10
v42.6.8
kojibai Jan 31, 2026
8ed468b
Hide proof controls for exhale note uploads
kojibai Jan 31, 2026
8c434ed
v42.6.9
kojibai Jan 31, 2026
822ac40
Merge pull request #338 from kojibai/codex/hide-signature-and-proof-b…
kojibai Jan 31, 2026
0e5e6df
v42.7.0
kojibai Jan 31, 2026
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
2 changes: 1 addition & 1 deletion public/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

// Update this version string manually to keep the app + cache versions in sync.
// The value is forwarded to the UI via the service worker "SW_ACTIVATED" message.
const APP_VERSION = "42.4.0"; // update on release
const APP_VERSION = "42.7.0"; // update on release
const VERSION = new URL(self.location.href).searchParams.get("v") || APP_VERSION; // derived from build
const PREFIX = "PHINETWORK";

Expand Down
2 changes: 1 addition & 1 deletion public/verifier.html
Original file line number Diff line number Diff line change
Expand Up @@ -2465,7 +2465,7 @@ <h3>Inhale • Verify • Remember • Exhale</h3>
}
}
function buildBanknoteSVG(){
const NOTE_TITLE = "KAIROS NOTE — LEGAL TENDER OF THE SOVEREIGN KINGDOM";
const NOTE_TITLE = "KAIROS NOTE — LEGAL TENDER OF THE SOVEREIGN KINGDOM";

/* Inputs */
const purposeRaw = $("note-purpose").value || "";
Expand Down
85 changes: 84 additions & 1 deletion server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from "node:fs/promises";
import fsSync from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { PassThrough } from "node:stream";
import { PassThrough, Readable } from "node:stream";
import { createServer as createHttpServer } from "node:http";
import { createServer as createViteServer } from "vite";
import { createHash } from "node:crypto";
Expand Down Expand Up @@ -32,6 +32,9 @@ const OG_PATH_PREFIX = "/og/v/verified/";
const OG_CACHE_CONTROL = "public, max-age=0, s-maxage=31536000, immutable";
const OG_CACHE_TTL_MS = 10 * 60 * 1000;
const OG_CACHE_MAX_ENTRIES = 512;
const SIGILS_PROXY_PATH = "/sigils";
const SIGILS_PRIMARY_BASE = "https://m.kai.ac";
const SIGILS_BACKUP_BASE = "https://memory.kaiklok.com";

const escapeHtml = (value) =>
String(value)
Expand Down Expand Up @@ -79,6 +82,84 @@ const tryServeStatic = (req, res, rootDir) => {
return true;
};

const shouldFailoverStatus = (status) => {
if (status === 0) return true;
if (status === 404) return true;
if (status === 408 || status === 429) return true;
if (status >= 500) return true;
return false;
};

const readRequestBody = async (req) => {
const chunks = [];
for await (const chunk of req) {
chunks.push(chunk);
}
return chunks.length ? Buffer.concat(chunks) : undefined;
};

const buildProxyHeaders = (req) => {
const headers = new Headers();
for (const [key, value] of Object.entries(req.headers)) {
const lower = key.toLowerCase();
if (lower === "host" || lower === "connection" || lower === "content-length") continue;
if (typeof value === "undefined") continue;
if (Array.isArray(value)) {
for (const entry of value) headers.append(lower, entry);
} else {
headers.set(lower, value);
}
}
return headers;
};

const proxySigils = async (req, res) => {
if (!req.url?.startsWith(SIGILS_PROXY_PATH)) return false;

const body =
req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS"
? undefined
: await readRequestBody(req);
const headers = buildProxyHeaders(req);
const bases = [SIGILS_PRIMARY_BASE, SIGILS_BACKUP_BASE];
let lastStatus = null;

for (const base of bases) {
try {
const proxyRes = await fetch(`${base}${req.url}`, {
method: req.method,
headers,
body,
});

if (shouldFailoverStatus(proxyRes.status) && base === SIGILS_PRIMARY_BASE) {
lastStatus = proxyRes.status;
continue;
}

res.statusCode = proxyRes.status;
for (const [key, value] of proxyRes.headers) {
if (key.toLowerCase() === "content-encoding") continue;
res.setHeader(key, value);
}

if (proxyRes.body) {
Readable.fromWeb(proxyRes.body).pipe(res);
return true;
}

res.end();
return true;
} catch {
continue;
}
}

res.statusCode = lastStatus ?? 502;
res.end("Bad Gateway");
return true;
};

async function createServer() {
let vite;
if (!isProd) {
Expand Down Expand Up @@ -243,6 +324,8 @@ async function createServer() {
return;
}

if (await proxySigils(req, res)) return;

if (await handleOgRoute(req, res)) return;

if (isProd) {
Expand Down
49 changes: 47 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,19 @@ const DEFAULT_LIVE_SNAP: LiveKaiSnap = {
dmyLabel: formatDMYLabel(DEFAULT_BEAT_STEP_DMY),
chakraDay: "Heart",
};
function isEditableElement(el: Element | null): boolean {
if (!el) return false;
if (el instanceof HTMLInputElement) return !el.disabled;
if (el instanceof HTMLTextAreaElement) return !el.disabled;
if (el instanceof HTMLSelectElement) return !el.disabled;
if (el instanceof HTMLElement && el.isContentEditable) return true;
return false;
}

function isEditingNow(): boolean {
if (typeof document === "undefined") return false;
return isEditableElement(document.activeElement as Element | null);
}

function computeBeatStepDMY(m: KaiMoment): BeatStepDMY {
const pulse = readNum(m, "pulse") ?? 0;
Expand Down Expand Up @@ -441,8 +454,40 @@ function ExplorerPopover({
children,
}: ExplorerPopoverProps): React.JSX.Element | null {
const hydrated = useHydrated();
const vvSizeRaw = useVisualViewportSize();
const vvSize = hydrated ? vvSizeRaw : { width: 0, height: 0 };
const vvSizeRaw = useVisualViewportSize();

// ✅ Freeze viewport height while typing (prevents iOS keyboard resize thrash)
const [vvStable, setVvStable] = useState<{ width: number; height: number }>({ width: 0, height: 0 });

useIsoLayoutEffect(() => {
if (!hydrated) return;
setVvStable({ width: vvSizeRaw.width, height: vvSizeRaw.height });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hydrated]);

useEffect(() => {
if (!hydrated) return;
if (isEditingNow()) return; // ✅ do not update while typing
setVvStable((prev) => {
if (prev.width === vvSizeRaw.width && prev.height === vvSizeRaw.height) return prev;
return { width: vvSizeRaw.width, height: vvSizeRaw.height };
});
}, [hydrated, vvSizeRaw.width, vvSizeRaw.height]);

useEffect(() => {
if (!hydrated) return;

const onFocusOut = (): void => {
// when keyboard closes, resync once
setVvStable({ width: vvSizeRaw.width, height: vvSizeRaw.height });
};

document.addEventListener("focusout", onFocusOut, true);
return () => document.removeEventListener("focusout", onFocusOut, true);
}, [hydrated, vvSizeRaw.width, vvSizeRaw.height]);

const vvSize = hydrated ? vvStable : { width: 0, height: 0 };


const portalHost = useMemo<HTMLElement | null>(() => {
if (!hydrated) return null;
Expand Down
Loading