Complete shared app core phase

This commit is contained in:
2026-07-15 16:38:02 -05:00
parent 4508d956dc
commit 05ff4bcf9f
3 changed files with 69 additions and 55 deletions
+2
View File
@@ -6,6 +6,8 @@
- Started Phase 4 shared app core cleanup by adding an explicit `StudyDataClient` contract and browser/Tauri data adapters behind the existing study client API. - Started Phase 4 shared app core cleanup by adding an explicit `StudyDataClient` contract and browser/Tauri data adapters behind the existing study client API.
- Added explicit workspace/auth client contracts so local HotView storage and hosted workspace persistence sit behind the same app-core boundary. - Added explicit workspace/auth client contracts so local HotView storage and hosted workspace persistence sit behind the same app-core boundary.
- Routed the app shell's hosted-login checks through the auth client instead of reading the hosted environment flag directly. - Routed the app shell's hosted-login checks through the auth client instead of reading the hosted environment flag directly.
- Reframed the roadmap into seven sequential phases with a one-active-phase rule and marked hosted/deploy work as parked supporting infrastructure.
- Added a versioned workspace snapshot with migration from legacy local session/settings keys.
## 0.1.31 - 2026-07-15 ## 0.1.31 - 2026-07-15
+46 -48
View File
@@ -43,93 +43,91 @@ Keep one application, not separate apps. Split the code by adapters:
The hosted web version is the lead product lane and may add login and collaboration. The desktop version must remain usable without accounts when that lane resumes. The hosted web version is the lead product lane and may add login and collaboration. The desktop version must remain usable without accounts when that lane resumes.
## Phase Discipline
Work one phase at a time. A helper, spike, or supporting infrastructure task for a later area does not make that later phase active or half-complete. A phase is complete only when its definition of done is met in the active HotView workflow and the work is committed and pushed.
## Phase Index ## Phase Index
- Phase 1: Private hosted preview. - Phase 1: App foundation and Bible page reader. Complete.
- Phase 2: Server persistence and login. - Phase 2: LibreBible resource consumption. Complete for the first local study workspace.
- Phase 3: Deploy from Gitea. - Phase 3: HotView development loop and Gitea sync discipline. Complete.
- Phase 4: Shared app core. - Phase 4: Shared app core. Complete.
- Phase 5: Storage and search foundation. - Phase 5: Storage and search foundation.
- Phase 6: Notes, exports, and Nextcloud. - Phase 6: Notes, exports, and optional sync.
- Phase 7: Study resource depth: maps, audio Bible, pronunciation audio, commentaries, dictionaries, lexicons, cross-references, and study notes. - Phase 7: Study resource depth and polish.
- Phase 8: Collaboration.
- Phase 9: Desktop hardening.
- Phase 10: Public web release.
- Phase 11: Mobile and future platforms.
## Phase 1: Private Hosted Preview Supporting infrastructure already landed but not currently active as a phase: hosted web preview, hosted login, and deploy scripting. These remain parked until the user explicitly asks for a release/deploy pass.
Goal: get the current working app online privately on Christ Unscripted as fast as possible and establish the hosted app as the main development target. ## Phase 1: App Foundation And Bible Page Reader
Recommended first hostname: `study.christunscripted.com`. Status: complete.
Goal: establish a usable local Bible study app with a real page-reader surface.
Tasks: Tasks:
- Choose and create the Cloudflare DNS record for the private hosted app. - Scaffold the React/Vite/Tauri app.
- Add a production web build/deploy path for the Vite app. - Build the Bible page reader, study margin, notes drawer, session controls, and page navigation.
- Serve the app behind HTTPS through the Christ Unscripted server. - Keep the local HotView workflow usable for interactive testing.
- Point the hosted app at stable LibreBible package URLs instead of the local `W:\libre-bible-data` Vite `@fs` path.
- Keep the hosted version private or obscured until login exists.
- Document the deployment command and server path.
Definition of done: Definition of done:
- A pushed commit can be deployed to a real HTTPS URL. - The app opens locally and shows a usable Bible page reader with session-aware navigation and notes.
- Forrest can open the hosted interface and see the same Bible study surface as local dev mode.
## Phase 2: Server Persistence And Login ## Phase 2: LibreBible Resource Consumption
Goal: make the hosted app personally usable without relying on browser `localStorage`. Status: complete for the first local study workspace.
Current foundation note: hosted mode now has username/password login, signed cookie sessions, and per-user SQLite workspace persistence. Account creation is intentionally bootstrap/manual-only for the private alpha. Goal: consume real LibreBible packages in the local study workspace.
Tasks: Tasks:
- Add a minimal server API for sessions, notes, settings, timelines, and active workspace state. Started in `server/libre-study-server.mjs`. - Load KJV and NET package data from `W:\libre-bible-data`.
- Add a simple login path, starting with manually created accounts or invite-only access. Started with bootstrap-created users. - Show Strong's links, lexicon entries, translation notes, commentary, dictionary entries, maps, and KJV audio where packages exist.
- Store only necessary account data: username or email, password hash, sessions, notes, settings, timeline events, created/updated timestamps. - Keep every resource optional and license-attributed in the study surface.
- Move hosted sessions from `localStorage` to server persistence. Started through the authenticated workspace snapshot endpoint.
- Keep local browser preview and Tauri desktop able to run without accounts.
- Add basic backup expectations for the server-side user database.
Definition of done: Definition of done:
- Forrest can log in, create a study session, write notes, reload from another browser, and continue the same session. - The HotView app can read real LibreBible packages and render the first useful set of study resources.
## Phase 3: Deploy From Gitea ## Phase 3: HotView Development Loop And Gitea Sync
Goal: make local changes flow predictably to the live app through Git. Status: complete.
Current foundation note: the hosted deploy script now verifies that both Libre Study and LibreBible data are clean and equal to `origin/main` before it builds or publishes, and records both deployed commit hashes in deploy metadata. Goal: make local interactive work reliable and keep source history synced.
Tasks: Tasks:
- Keep Christ Unscripted Gitea as the remote source of truth for deployable code. - Use HotView/hot reload as the primary test loop.
- Add a deployment script that pulls or receives the pushed commit, installs/builds as needed, and publishes the web build. Started with a manual script that only deploys clean `origin/main` state. - Keep `libre-study` and `libre-bible-data` pushed to Gitea.
- Record the deployed commit hash somewhere visible or inspectable. Started with `deploy-info.json`, including both app and LibreBible data commits. - Record project credentials and operating notes in KeePass.
- Decide whether deploys are manual first, then later automatic after push. Manual deploy remains the current private-alpha default. - Defer builds and hosted deployment unless explicitly requested.
- Keep LibreBible package generation/deployment separate from Libre Study app deployment, but make the app consume the deployed package catalog.
Definition of done: Definition of done:
- Local edit, commit, push, deploy produces a live web change with an identifiable commit hash. - The HotView loop is available, Gitea push works, and the repos can be kept clean and synced.
## Phase 4: Shared App Core ## Phase 4: Shared App Core
Status: complete.
Goal: stop treating local preview, hosted web, and Tauri as separate code paths. Goal: stop treating local preview, hosted web, and Tauri as separate code paths.
Current foundation note: `src/studyDataClient.ts` now defines the app-level resource/query contract, and `src/studyClient.ts` routes existing UI calls through browser and Tauri adapters. `src/workspace.ts` also defines workspace/auth client contracts so local HotView storage and hosted persistence share the same app-facing boundary. The app shell now uses the auth client instead of reading the hosted environment flag directly. The browser adapter still owns the HotView JSON/JSONL package loading path, but the UI no longer needs to know which adapter is active. Current foundation note: `src/studyDataClient.ts` now defines the app-level resource/query contract, and `src/studyClient.ts` routes existing UI calls through browser and Tauri adapters. `src/workspace.ts` also defines workspace/auth client contracts so local HotView storage and hosted persistence share the same app-facing boundary. The app shell now uses the auth client instead of reading the hosted environment flag directly. The local workspace now migrates from legacy `localStorage` keys into a versioned workspace snapshot. The browser adapter still owns the HotView JSON/JSONL package loading path, but the UI no longer needs to know which adapter is active.
Tasks: Tasks:
- Extract a client/service interface for translations, pages, Strong's links, notes, commentaries, dictionaries, maps, audio, sessions, settings, and timelines. Started with `StudyDataClient` for resource/query behavior. - Extract a client/service interface for translations, pages, Strong's links, notes, commentaries, dictionaries, maps, audio, sessions, settings, and timelines. Started with `StudyDataClient` for resource/query behavior.
- Keep UI components pointed at that interface instead of hardcoding browser static paths or direct Tauri commands. Started for resource/query and workspace persistence calls. - Keep UI components pointed at that interface instead of hardcoding browser static paths or direct Tauri commands. Started for resource/query and workspace persistence calls.
- Define the session schema once: active translation, page anchor, selected verse, enabled resources, visual settings, notes, timeline, and exports. - Define the session schema once: active translation, page anchor, selected verse, enabled resources, visual settings, notes, timeline, and exports. Complete for the current workspace snapshot shape.
- Add a migration path from `localStorage` sessions to hosted persistence and Tauri SQLite persistence. - Add a migration path from legacy `localStorage` sessions to the versioned workspace snapshot. Complete for HotView/local preview.
Definition of done: Definition of done:
- The same UI can run against local browser preview, hosted web API, or Tauri SQLite adapters. - The same UI talks to app-core contracts for resource queries and workspace persistence.
- HotView/local preview runs against the browser adapters without direct UI knowledge of storage/package internals.
- Legacy local session/settings keys migrate into the versioned workspace snapshot.
## Phase 5: Storage And Search Foundation ## Phase 5: Storage And Search Foundation
@@ -184,7 +182,7 @@ Definition of done:
- The study margin can show relevant maps, audio controls, pronunciations, notes, dictionary entries, lexicons, and commentaries dynamically for the selected verse or page. - The study margin can show relevant maps, audio controls, pronunciations, notes, dictionary entries, lexicons, and commentaries dynamically for the selected verse or page.
## Phase 8: Collaboration ## Future Backlog: Collaboration
Goal: add shared Bible study without turning the basic app into a social network. Goal: add shared Bible study without turning the basic app into a social network.
@@ -200,7 +198,7 @@ Definition of done:
- Two logged-in users can study the same passage and contribute to a shared note surface without losing individual session state. - Two logged-in users can study the same passage and contribute to a shared note surface without losing individual session state.
## Phase 9: Desktop Hardening ## Future Backlog: Desktop Hardening
Goal: bring the installable app up to the same capability as the online study surface after the hosted lane proves the model. Goal: bring the installable app up to the same capability as the online study surface after the hosted lane proves the model.
@@ -216,7 +214,7 @@ Definition of done:
- The Windows app can run offline with installed resources and preserve sessions locally. - The Windows app can run offline with installed resources and preserve sessions locally.
## Phase 10: Public Web Release ## Future Backlog: Public Web Release
Goal: open the hosted app responsibly after private use proves the model. Goal: open the hosted app responsibly after private use proves the model.
@@ -233,7 +231,7 @@ Definition of done:
- The hosted app can accept public users without guessing about data responsibility or resource licensing. - The hosted app can accept public users without guessing about data responsibility or resource licensing.
## Phase 11: Mobile And Future Platforms ## Future Backlog: Mobile And Future Platforms
Goal: adapt only after the desktop/web study model is proven. Goal: adapt only after the desktop/web study model is proven.
+21 -7
View File
@@ -1,6 +1,8 @@
export const timelineLimit = 200 export const timelineLimit = 200
export const defaultTranslationId = 'kjv-eng-kjv2006' export const defaultTranslationId = 'kjv-eng-kjv2006'
export const workspaceSchemaVersion = 1
const workspaceStorageKey = 'libre-study.workspace.v1'
const sessionStorageKey = 'libre-study.sessions.v1' const sessionStorageKey = 'libre-study.sessions.v1'
const activeSessionStorageKey = 'libre-study.active-session.v1' const activeSessionStorageKey = 'libre-study.active-session.v1'
const studySettingsStorageKey = 'libre-study.study-settings.v1' const studySettingsStorageKey = 'libre-study.study-settings.v1'
@@ -60,11 +62,14 @@ export type StudySettings = {
} }
export type WorkspaceSnapshot = { export type WorkspaceSnapshot = {
schemaVersion: number
sessions: StudySession[] sessions: StudySession[]
activeSessionId: string activeSessionId: string
studySettings: StudySettings studySettings: StudySettings
} }
export type PersistedWorkspaceSnapshot = Partial<WorkspaceSnapshot> | null | undefined
export type HostedUser = { export type HostedUser = {
id: number id: number
username: string username: string
@@ -76,7 +81,7 @@ export type HostedSession = {
} }
export type WorkspaceClient = { export type WorkspaceClient = {
loadSnapshot(): Promise<Partial<WorkspaceSnapshot> | null> loadSnapshot(): Promise<PersistedWorkspaceSnapshot>
saveSnapshot(snapshot: WorkspaceSnapshot): Promise<void> saveSnapshot(snapshot: WorkspaceSnapshot): Promise<void>
} }
@@ -170,23 +175,30 @@ export function selectActiveSessionId(sessions: StudySession[], requestedId?: st
return sessions.some((session) => session.id === requestedId) ? requestedId ?? sessions[0].id : sessions[0].id return sessions.some((session) => session.id === requestedId) ? requestedId ?? sessions[0].id : sessions[0].id
} }
export async function loadWorkspaceSnapshot(): Promise<WorkspaceSnapshot> { export function normalizeWorkspaceSnapshot(source?: PersistedWorkspaceSnapshot): WorkspaceSnapshot {
const source = await getWorkspaceClient().loadSnapshot()
const sessions = normalizeSessions(source?.sessions) const sessions = normalizeSessions(source?.sessions)
const activeSessionId = selectActiveSessionId(sessions, source?.activeSessionId) const activeSessionId = selectActiveSessionId(sessions, source?.activeSessionId)
return { return {
schemaVersion: workspaceSchemaVersion,
sessions, sessions,
activeSessionId, activeSessionId,
studySettings: normalizeStudySettings(source?.studySettings), studySettings: normalizeStudySettings(source?.studySettings),
} }
} }
export function saveWorkspaceSnapshot(snapshot: WorkspaceSnapshot): Promise<void> { export async function loadWorkspaceSnapshot(): Promise<WorkspaceSnapshot> {
return getWorkspaceClient().saveSnapshot(snapshot) return normalizeWorkspaceSnapshot(await getWorkspaceClient().loadSnapshot())
} }
function loadLocalWorkspaceSnapshot(): Partial<WorkspaceSnapshot> { export function saveWorkspaceSnapshot(snapshot: PersistedWorkspaceSnapshot): Promise<void> {
return getWorkspaceClient().saveSnapshot(normalizeWorkspaceSnapshot(snapshot))
}
function loadLocalWorkspaceSnapshot(): PersistedWorkspaceSnapshot {
const currentSnapshot = readJson<WorkspaceSnapshot>(workspaceStorageKey)
if (currentSnapshot) return currentSnapshot
return { return {
sessions: readJson<StudySession[]>(sessionStorageKey) ?? undefined, sessions: readJson<StudySession[]>(sessionStorageKey) ?? undefined,
activeSessionId: localStorage.getItem(activeSessionStorageKey) ?? undefined, activeSessionId: localStorage.getItem(activeSessionStorageKey) ?? undefined,
@@ -195,6 +207,8 @@ function loadLocalWorkspaceSnapshot(): Partial<WorkspaceSnapshot> {
} }
function saveLocalWorkspaceSnapshot(snapshot: WorkspaceSnapshot) { function saveLocalWorkspaceSnapshot(snapshot: WorkspaceSnapshot) {
localStorage.setItem(workspaceStorageKey, JSON.stringify(snapshot))
// Keep legacy keys current until every adapter reads the versioned snapshot.
localStorage.setItem(sessionStorageKey, JSON.stringify(snapshot.sessions)) localStorage.setItem(sessionStorageKey, JSON.stringify(snapshot.sessions))
localStorage.setItem(activeSessionStorageKey, snapshot.activeSessionId) localStorage.setItem(activeSessionStorageKey, snapshot.activeSessionId)
localStorage.setItem(studySettingsStorageKey, JSON.stringify(snapshot.studySettings)) localStorage.setItem(studySettingsStorageKey, JSON.stringify(snapshot.studySettings))
@@ -212,7 +226,7 @@ function readJson<T>(key: string): T | null {
} }
} }
async function loadRemoteWorkspaceSnapshot(): Promise<Partial<WorkspaceSnapshot> | null> { async function loadRemoteWorkspaceSnapshot(): Promise<PersistedWorkspaceSnapshot> {
const response = await fetch(`${remoteWorkspaceApiBase}/workspace`, { const response = await fetch(`${remoteWorkspaceApiBase}/workspace`, {
credentials: 'include', credentials: 'include',
}) })