Complete shared app core phase
This commit is contained in:
@@ -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
@@ -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
@@ -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',
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user