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.
- 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.
- 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
+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.
## 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 1: Private hosted preview.
- Phase 2: Server persistence and login.
- Phase 3: Deploy from Gitea.
- Phase 4: Shared app core.
- Phase 1: App foundation and Bible page reader. Complete.
- Phase 2: LibreBible resource consumption. Complete for the first local study workspace.
- Phase 3: HotView development loop and Gitea sync discipline. Complete.
- Phase 4: Shared app core. Complete.
- Phase 5: Storage and search foundation.
- Phase 6: Notes, exports, and Nextcloud.
- Phase 7: Study resource depth: maps, audio Bible, pronunciation audio, commentaries, dictionaries, lexicons, cross-references, and study notes.
- Phase 8: Collaboration.
- Phase 9: Desktop hardening.
- Phase 10: Public web release.
- Phase 11: Mobile and future platforms.
- Phase 6: Notes, exports, and optional sync.
- Phase 7: Study resource depth and polish.
## 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:
- Choose and create the Cloudflare DNS record for the private hosted app.
- Add a production web build/deploy path for the Vite app.
- Serve the app behind HTTPS through the Christ Unscripted server.
- 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.
- Scaffold the React/Vite/Tauri app.
- Build the Bible page reader, study margin, notes drawer, session controls, and page navigation.
- Keep the local HotView workflow usable for interactive testing.
Definition of done:
- A pushed commit can be deployed to a real HTTPS URL.
- Forrest can open the hosted interface and see the same Bible study surface as local dev mode.
- The app opens locally and shows a usable Bible page reader with session-aware navigation and notes.
## 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:
- Add a minimal server API for sessions, notes, settings, timelines, and active workspace state. Started in `server/libre-study-server.mjs`.
- Add a simple login path, starting with manually created accounts or invite-only access. Started with bootstrap-created users.
- Store only necessary account data: username or email, password hash, sessions, notes, settings, timeline events, created/updated timestamps.
- 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.
- Load KJV and NET package data from `W:\libre-bible-data`.
- Show Strong's links, lexicon entries, translation notes, commentary, dictionary entries, maps, and KJV audio where packages exist.
- Keep every resource optional and license-attributed in the study surface.
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:
- Keep Christ Unscripted Gitea as the remote source of truth for deployable code.
- 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.
- Record the deployed commit hash somewhere visible or inspectable. Started with `deploy-info.json`, including both app and LibreBible data commits.
- Decide whether deploys are manual first, then later automatic after push. Manual deploy remains the current private-alpha default.
- Keep LibreBible package generation/deployment separate from Libre Study app deployment, but make the app consume the deployed package catalog.
- Use HotView/hot reload as the primary test loop.
- Keep `libre-study` and `libre-bible-data` pushed to Gitea.
- Record project credentials and operating notes in KeePass.
- Defer builds and hosted deployment unless explicitly requested.
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
Status: complete.
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:
- 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.
- Define the session schema once: active translation, page anchor, selected verse, enabled resources, visual settings, notes, timeline, and exports.
- Add a migration path from `localStorage` sessions to hosted persistence and Tauri SQLite persistence.
- 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 legacy `localStorage` sessions to the versioned workspace snapshot. Complete for HotView/local preview.
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
@@ -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.
## Phase 8: Collaboration
## Future Backlog: Collaboration
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.
## 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.
@@ -216,7 +214,7 @@ Definition of done:
- 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.
@@ -233,7 +231,7 @@ Definition of done:
- 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.
+21 -7
View File
@@ -1,6 +1,8 @@
export const timelineLimit = 200
export const defaultTranslationId = 'kjv-eng-kjv2006'
export const workspaceSchemaVersion = 1
const workspaceStorageKey = 'libre-study.workspace.v1'
const sessionStorageKey = 'libre-study.sessions.v1'
const activeSessionStorageKey = 'libre-study.active-session.v1'
const studySettingsStorageKey = 'libre-study.study-settings.v1'
@@ -60,11 +62,14 @@ export type StudySettings = {
}
export type WorkspaceSnapshot = {
schemaVersion: number
sessions: StudySession[]
activeSessionId: string
studySettings: StudySettings
}
export type PersistedWorkspaceSnapshot = Partial<WorkspaceSnapshot> | null | undefined
export type HostedUser = {
id: number
username: string
@@ -76,7 +81,7 @@ export type HostedSession = {
}
export type WorkspaceClient = {
loadSnapshot(): Promise<Partial<WorkspaceSnapshot> | null>
loadSnapshot(): Promise<PersistedWorkspaceSnapshot>
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
}
export async function loadWorkspaceSnapshot(): Promise<WorkspaceSnapshot> {
const source = await getWorkspaceClient().loadSnapshot()
export function normalizeWorkspaceSnapshot(source?: PersistedWorkspaceSnapshot): WorkspaceSnapshot {
const sessions = normalizeSessions(source?.sessions)
const activeSessionId = selectActiveSessionId(sessions, source?.activeSessionId)
return {
schemaVersion: workspaceSchemaVersion,
sessions,
activeSessionId,
studySettings: normalizeStudySettings(source?.studySettings),
}
}
export function saveWorkspaceSnapshot(snapshot: WorkspaceSnapshot): Promise<void> {
return getWorkspaceClient().saveSnapshot(snapshot)
export async function loadWorkspaceSnapshot(): Promise<WorkspaceSnapshot> {
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 {
sessions: readJson<StudySession[]>(sessionStorageKey) ?? undefined,
activeSessionId: localStorage.getItem(activeSessionStorageKey) ?? undefined,
@@ -195,6 +207,8 @@ function loadLocalWorkspaceSnapshot(): Partial<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(activeSessionStorageKey, snapshot.activeSessionId)
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`, {
credentials: 'include',
})