feat(ui): offline-first foundations — bundled dicts + local-guest identity
The additive groundwork for the native offline-first experience (ANDROID_PLAN.md §D). Inert until the native cold-boot lands: on the web these paths never fire, so web / VK / Telegram behaviour is unchanged. - ui/scripts/bundle-dicts.mjs copies the scrabble-dictionary release DAWGs into dist/dict/<variant>@<version>.dawg for the native pipeline (run after `pnpm build`, before `cap sync`). - The dict loader gains a bundled tier between the IndexedDB and network tiers, attempted only on a native channel (a packaged app serves ./dict/<key>.dawg from its assets). Native-gated rather than the plan's "404 on the web" because the relative path would otherwise hit the gateway's own session-gated /dict/ route. - __DICT_VERSION__ Vite define (from VITE_DICT_VERSION, default "dev") + its ambient declaration; the offline vs_ai / hotseat creates in NewGame fall back to it when a device-local guest has no profile-advertised version. - New lib/localguest.ts: a persisted device-local guest id (no DB row); the offline vs_ai human seat uses it (and the localized common.guest name) when there is no server session. svelte-check clean, vitest green, native + web builds clean. The cold-boot rewrite, reconciliation, the Profile soft-sign-in gating and the offline-first e2e follow.
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
// Copies the production dictionary DAWGs into the build's app assets so a native (offline-first)
|
||||
// install can obtain a dictionary with no network: the loader's bundled tier fetches
|
||||
// ./dict/<dictKey>.dawg, where dictKey is "<variant>@<version>" (see lib/dict/store.ts). Run only in
|
||||
// the native pipeline (after `pnpm build`, before `cap sync`); web builds skip it and stay slim.
|
||||
//
|
||||
// Source: DICT_DIR — the unpacked scrabble-dictionary release (scrabble-dawg-<DICT_VERSION>.tar.gz),
|
||||
// the SAME set the backend image and CI consume, NOT the solver's committed test fixtures. The bundled
|
||||
// version label comes from VITE_DICT_VERSION (default "dev") and MUST equal the client's __DICT_VERSION__
|
||||
// so the loader requests the exact (variant, version) the file is named for. OUT_DIR overrides the
|
||||
// output root (default `dist`) — the e2e build points it at dist-e2e.
|
||||
|
||||
import { existsSync, mkdirSync, copyFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const srcDir = process.env.DICT_DIR;
|
||||
if (!srcDir) {
|
||||
console.error('bundle-dicts: DICT_DIR is required (the unpacked scrabble-dictionary release dir)');
|
||||
process.exit(1);
|
||||
}
|
||||
const version = process.env.VITE_DICT_VERSION || 'dev';
|
||||
const outDir = join(process.env.OUT_DIR || 'dist', 'dict');
|
||||
|
||||
// The app's Variant enum value -> the release dawg file name (matches e2e-dict.mjs and the movegen
|
||||
// parity mapping).
|
||||
const dawgFor = {
|
||||
scrabble_en: 'en_sowpods',
|
||||
scrabble_ru: 'ru_scrabble',
|
||||
erudit_ru: 'ru_erudit',
|
||||
};
|
||||
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
let copied = 0;
|
||||
for (const [variant, file] of Object.entries(dawgFor)) {
|
||||
const src = join(srcDir, `${file}.dawg`);
|
||||
if (!existsSync(src)) {
|
||||
console.warn(`bundle-dicts: missing ${src} — the bundled tier will 404 for ${variant} (set DICT_DIR)`);
|
||||
continue;
|
||||
}
|
||||
copyFileSync(src, join(outDir, `${variant}@${version}.dawg`));
|
||||
copied++;
|
||||
}
|
||||
console.log(`bundle-dicts: copied ${copied}/${Object.keys(dawgFor).length} dawgs -> ${outDir} (version ${version})`);
|
||||
Reference in New Issue
Block a user