refactor(client): migrate ui-state.js → client/ui-state.ts (first real
frontend module conversion) ui-state was the smallest, most isolated module: 35 lines, zero legacy- global dependencies, single concern (localStorage helpers under the 'ped_ui/' namespace). Ideal first migration to validate the strangler- fig pattern end-to-end. Changes: - New: client/ui-state.ts — exports get/set/del + UIState bundle. Keeps (window as any).UIState = UIState assignment so the 10+ legacy consumers (sub-nav.js, peGuide.js, app.js, etc.) keep working without modification. As each consumer is migrated, it will replace the window.UIState read with an explicit import. - client/main.ts: import './ui-state' so it gets bundled into main.js. - scripts/build-client.mjs: explicit ENTRIES list (just 'main.ts' for now) — was scanning every top-level .ts as an entry, which produced duplicate ui-state.js bundle. - public/index.html: <script defer src="/js/ui-state.js"> removed; the TS bundle (/dist/main.js) now provides window.UIState. - public/js/ui-state.js: deleted (now lives in client/). Verification: - npm run typecheck:client → 0 errors - npm run build:client → 349-byte main.js bundle (gzipped will be ~200B) - 46/46 unit tests pass - The 10 cross-file consumers of UIState.get/set/del still work because the window assignment lives in the bundle. Pattern documented for next migrations: each new client/X.ts is imported as a side-effect from main.ts. The window-bridge stays until every consumer also moves to ES imports.
This commit is contained in:
parent
759135f76f
commit
ce31a1e343
5 changed files with 57 additions and 46 deletions
|
|
@ -18,6 +18,10 @@
|
||||||
// (voice/STT plumbing)
|
// (voice/STT plumbing)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
// Migrated modules — each side-effect import installs its window-global
|
||||||
|
// shim for legacy consumers and self-registers any DOM listeners.
|
||||||
|
import './ui-state';
|
||||||
|
|
||||||
console.log('[client] TypeScript bundle loaded.');
|
console.log('[client] TypeScript bundle loaded.');
|
||||||
|
|
||||||
// Marker so anything else that wants to know if the bundle reached
|
// Marker so anything else that wants to know if the bundle reached
|
||||||
|
|
|
||||||
37
client/ui-state.ts
Normal file
37
client/ui-state.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
// ============================================================
|
||||||
|
// client/ui-state.ts — sub-pill / sub-tab persistence across browser restart.
|
||||||
|
//
|
||||||
|
// First file migrated out of public/js/ as ES module + TypeScript.
|
||||||
|
// Tab-level selection (ped_last_tab) is handled in app.js (still legacy).
|
||||||
|
// This file persists finer-grained choices that would otherwise reset
|
||||||
|
// to defaults when the user closes the browser, signs out / back in,
|
||||||
|
// or reloads the page — because _componentCache is in-memory only.
|
||||||
|
//
|
||||||
|
// All keys live under "ped_ui/" in localStorage. Reads and writes are
|
||||||
|
// wrapped in try/catch — localStorage can throw in some contexts
|
||||||
|
// (Safari private mode, storage quotas), and a persistence failure
|
||||||
|
// must never break the UI.
|
||||||
|
//
|
||||||
|
// Compat: window.UIState is still assigned for the 10+ legacy consumers
|
||||||
|
// (app.js, sub-nav.js, several IIFE modules). Once those are migrated
|
||||||
|
// to ES module imports, the window assignment can be removed.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const PREFIX = 'ped_ui/';
|
||||||
|
|
||||||
|
export function get(key: string): string | null {
|
||||||
|
try { return localStorage.getItem(PREFIX + key); } catch (e) { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function set(key: string, value: string): void {
|
||||||
|
try { localStorage.setItem(PREFIX + key, value); } catch (e) { /* swallow — see header */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function del(key: string): void {
|
||||||
|
try { localStorage.removeItem(PREFIX + key); } catch (e) { /* swallow */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UIState = { get, set, del };
|
||||||
|
|
||||||
|
// Legacy bridge — kept until every consumer is migrated to ES imports.
|
||||||
|
(window as any).UIState = UIState;
|
||||||
|
|
@ -452,7 +452,7 @@
|
||||||
<script defer src="/js/transcriptionSettings.js"></script>
|
<script defer src="/js/transcriptionSettings.js"></script>
|
||||||
<script defer src="/js/voicePreferences.js"></script>
|
<script defer src="/js/voicePreferences.js"></script>
|
||||||
<script defer src="/js/app.js"></script>
|
<script defer src="/js/app.js"></script>
|
||||||
<script defer src="/js/ui-state.js"></script>
|
<!-- ui-state.js migrated → client/ui-state.ts (loaded via /dist/main.js) -->
|
||||||
<script defer src="/js/secureStorage.js"></script>
|
<script defer src="/js/secureStorage.js"></script>
|
||||||
<script defer src="/js/authFetch.js"></script>
|
<script defer src="/js/authFetch.js"></script>
|
||||||
<script defer src="/js/auth.js"></script>
|
<script defer src="/js/auth.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
// ============================================================
|
|
||||||
// UI STATE — sub-pill / sub-tab persistence across browser restart
|
|
||||||
// ============================================================
|
|
||||||
// Tab-level selection (ped_last_tab) is handled in app.js. This file
|
|
||||||
// persists the finer-grained choices that would otherwise reset to
|
|
||||||
// defaults when the user closes the browser, signs out / back in, or
|
|
||||||
// reloads the page — because _componentCache is in-memory and cleared
|
|
||||||
// on reload.
|
|
||||||
//
|
|
||||||
// All keys live under the "ped_ui/" namespace in localStorage so they
|
|
||||||
// are easy to enumerate and clear. Reads and writes are wrapped in
|
|
||||||
// try/catch — localStorage can throw in some contexts (Safari private
|
|
||||||
// mode, storage quotas), and a persistence failure must never break
|
|
||||||
// the UI.
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
var PREFIX = 'ped_ui/';
|
|
||||||
|
|
||||||
function get(key) {
|
|
||||||
try { return localStorage.getItem(PREFIX + key); } catch (e) { return null; }
|
|
||||||
}
|
|
||||||
function set(key, value) {
|
|
||||||
try { localStorage.setItem(PREFIX + key, value); } catch (e) {}
|
|
||||||
}
|
|
||||||
function del(key) {
|
|
||||||
try { localStorage.removeItem(PREFIX + key); } catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
window.UIState = {
|
|
||||||
get: get,
|
|
||||||
set: set,
|
|
||||||
del: del,
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
@ -31,17 +31,22 @@ if (!existsSync(SRC)) {
|
||||||
}
|
}
|
||||||
mkdirSync(OUT, { recursive: true });
|
mkdirSync(OUT, { recursive: true });
|
||||||
|
|
||||||
// Each top-level .ts/.tsx file in client/ is a separate entry point.
|
// Explicit entry list — every other .ts file in client/ is a sub-module
|
||||||
// Sub-directories are imported by the entries via TS imports — they
|
// imported by an entry. Add new entries here when a feature truly needs
|
||||||
// are NOT separate entries, they get bundled into their parent.
|
// its own separate bundle (different DOM lifecycle, lazy-loaded route,
|
||||||
const entryFiles = readdirSync(SRC, { withFileTypes: true })
|
// etc.). For most modernization work, just import into main.ts.
|
||||||
.filter(d => d.isFile() && /\.tsx?$/.test(d.name))
|
const ENTRIES = [
|
||||||
.map(d => resolve(SRC, d.name));
|
'main.ts',
|
||||||
|
];
|
||||||
|
|
||||||
if (entryFiles.length === 0) {
|
const entryFiles = ENTRIES.map(name => {
|
||||||
console.error(`[build-client] No .ts entry points found in ${SRC}`);
|
const p = resolve(SRC, name);
|
||||||
process.exit(1);
|
if (!existsSync(p)) {
|
||||||
}
|
console.error(`[build-client] Entry file does not exist: ${p}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
|
||||||
const buildOpts = {
|
const buildOpts = {
|
||||||
entryPoints: entryFiles,
|
entryPoints: entryFiles,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue