Add workspace client adapters
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- Recorded the active development direction: use the local HotView/hot-reload setup for iterative app work and defer web/local builds and hosted deployment unless explicitly requested.
|
- Recorded the active development direction: use the local HotView/hot-reload setup for iterative app work and defer web/local builds and hosted deployment unless explicitly requested.
|
||||||
- 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.
|
||||||
|
|
||||||
## 0.1.31 - 2026-07-15
|
## 0.1.31 - 2026-07-15
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -118,12 +118,12 @@ Definition of done:
|
|||||||
|
|
||||||
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. 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 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.
|
- 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.
|
||||||
- Add a migration path from `localStorage` sessions to hosted persistence and Tauri SQLite persistence.
|
- Add a migration path from `localStorage` sessions to hosted persistence and Tauri SQLite persistence.
|
||||||
|
|
||||||
|
|||||||
+71
-11
@@ -75,6 +75,18 @@ export type HostedSession = {
|
|||||||
user: HostedUser | null
|
user: HostedUser | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorkspaceClient = {
|
||||||
|
loadSnapshot(): Promise<Partial<WorkspaceSnapshot> | null>
|
||||||
|
saveSnapshot(snapshot: WorkspaceSnapshot): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthClient = {
|
||||||
|
enabled: boolean
|
||||||
|
loadSession(): Promise<HostedSession>
|
||||||
|
login(username: string, password: string): Promise<HostedSession>
|
||||||
|
logout(): Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
export function hostedWorkspaceEnabled() {
|
export function hostedWorkspaceEnabled() {
|
||||||
return remoteWorkspaceEnabled
|
return remoteWorkspaceEnabled
|
||||||
}
|
}
|
||||||
@@ -159,9 +171,7 @@ export function selectActiveSessionId(sessions: StudySession[], requestedId?: st
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loadWorkspaceSnapshot(): Promise<WorkspaceSnapshot> {
|
export async function loadWorkspaceSnapshot(): Promise<WorkspaceSnapshot> {
|
||||||
const remoteSnapshot = remoteWorkspaceEnabled ? await loadRemoteWorkspaceSnapshot() : null
|
const source = await getWorkspaceClient().loadSnapshot()
|
||||||
const localSnapshot = remoteSnapshot ? null : loadLocalWorkspaceSnapshot()
|
|
||||||
const source = remoteSnapshot ?? localSnapshot
|
|
||||||
const sessions = normalizeSessions(source?.sessions)
|
const sessions = normalizeSessions(source?.sessions)
|
||||||
const activeSessionId = selectActiveSessionId(sessions, source?.activeSessionId)
|
const activeSessionId = selectActiveSessionId(sessions, source?.activeSessionId)
|
||||||
|
|
||||||
@@ -173,9 +183,7 @@ export async function loadWorkspaceSnapshot(): Promise<WorkspaceSnapshot> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function saveWorkspaceSnapshot(snapshot: WorkspaceSnapshot): Promise<void> {
|
export function saveWorkspaceSnapshot(snapshot: WorkspaceSnapshot): Promise<void> {
|
||||||
saveLocalWorkspaceSnapshot(snapshot)
|
return getWorkspaceClient().saveSnapshot(snapshot)
|
||||||
if (!remoteWorkspaceEnabled) return Promise.resolve()
|
|
||||||
return saveRemoteWorkspaceSnapshot(snapshot)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadLocalWorkspaceSnapshot(): Partial<WorkspaceSnapshot> {
|
function loadLocalWorkspaceSnapshot(): Partial<WorkspaceSnapshot> {
|
||||||
@@ -234,8 +242,49 @@ async function saveRemoteWorkspaceSnapshot(snapshot: WorkspaceSnapshot) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadHostedSession(): Promise<HostedSession> {
|
const localWorkspaceClient: WorkspaceClient = {
|
||||||
if (!remoteWorkspaceEnabled) return { authenticated: false, user: null }
|
loadSnapshot: async () => loadLocalWorkspaceSnapshot(),
|
||||||
|
saveSnapshot: async (snapshot) => {
|
||||||
|
saveLocalWorkspaceSnapshot(snapshot)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostedWorkspaceClient: WorkspaceClient = {
|
||||||
|
loadSnapshot: async () => {
|
||||||
|
const remoteSnapshot = await loadRemoteWorkspaceSnapshot()
|
||||||
|
return remoteSnapshot ?? loadLocalWorkspaceSnapshot()
|
||||||
|
},
|
||||||
|
saveSnapshot: async (snapshot) => {
|
||||||
|
saveLocalWorkspaceSnapshot(snapshot)
|
||||||
|
await saveRemoteWorkspaceSnapshot(snapshot)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const inactiveAuthClient: AuthClient = {
|
||||||
|
enabled: false,
|
||||||
|
loadSession: async () => ({ authenticated: false, user: null }),
|
||||||
|
login: async () => {
|
||||||
|
throw new Error('Hosted login is not enabled in this workspace mode.')
|
||||||
|
},
|
||||||
|
logout: async () => undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const hostedAuthClient: AuthClient = {
|
||||||
|
enabled: true,
|
||||||
|
loadSession: loadRemoteHostedSession,
|
||||||
|
login: loginRemoteHostedWorkspace,
|
||||||
|
logout: logoutRemoteHostedWorkspace,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorkspaceClient(): WorkspaceClient {
|
||||||
|
return remoteWorkspaceEnabled ? hostedWorkspaceClient : localWorkspaceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAuthClient(): AuthClient {
|
||||||
|
return remoteWorkspaceEnabled ? hostedAuthClient : inactiveAuthClient
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadRemoteHostedSession(): Promise<HostedSession> {
|
||||||
const response = await fetch(`${remoteWorkspaceApiBase}/session`, {
|
const response = await fetch(`${remoteWorkspaceApiBase}/session`, {
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
})
|
})
|
||||||
@@ -245,7 +294,7 @@ export async function loadHostedSession(): Promise<HostedSession> {
|
|||||||
return response.json() as Promise<HostedSession>
|
return response.json() as Promise<HostedSession>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loginHostedWorkspace(username: string, password: string): Promise<HostedSession> {
|
async function loginRemoteHostedWorkspace(username: string, password: string): Promise<HostedSession> {
|
||||||
const response = await fetch(`${remoteWorkspaceApiBase}/login`, {
|
const response = await fetch(`${remoteWorkspaceApiBase}/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
@@ -260,8 +309,7 @@ export async function loginHostedWorkspace(username: string, password: string):
|
|||||||
return response.json() as Promise<HostedSession>
|
return response.json() as Promise<HostedSession>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logoutHostedWorkspace(): Promise<void> {
|
async function logoutRemoteHostedWorkspace(): Promise<void> {
|
||||||
if (!remoteWorkspaceEnabled) return
|
|
||||||
const response = await fetch(`${remoteWorkspaceApiBase}/logout`, {
|
const response = await fetch(`${remoteWorkspaceApiBase}/logout`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
@@ -270,3 +318,15 @@ export async function logoutHostedWorkspace(): Promise<void> {
|
|||||||
throw new Error(`Unable to log out: ${response.status} ${response.statusText}`)
|
throw new Error(`Unable to log out: ${response.status} ${response.statusText}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function loadHostedSession(): Promise<HostedSession> {
|
||||||
|
return getAuthClient().loadSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loginHostedWorkspace(username: string, password: string): Promise<HostedSession> {
|
||||||
|
return getAuthClient().login(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logoutHostedWorkspace(): Promise<void> {
|
||||||
|
return getAuthClient().logout()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user