Route application diagnostics through owned log paths

This commit is contained in:
vorotamoroz
2026-07-18 16:53:20 +00:00
parent 3e22c2eda8
commit 81d27a932f
22 changed files with 154 additions and 105 deletions
@@ -2,7 +2,7 @@
## Status
Proposed — the implementation proof is complete, and `@vrtmrz/livesync-commonlib@0.1.0-rc.2` has been published and verified as the exact downstream dependency; the community-scanner preview remains pending.
Proposed — the implementation proof is complete, `@vrtmrz/livesync-commonlib@0.1.0-rc.2` has been published and verified as the exact downstream dependency, and the Community directory scanner preview confirms the intended package boundary.
## Context
@@ -255,7 +255,11 @@ The Commonlib contract suite passes 21 tests covering Context results, both plat
The package-owned Trystero transport also completes the canonical Compose P2P synchronisation workflow with a local relay and two isolated CLI peers. In real Obsidian, the plug-in starts with one consistent `ObsidianServiceContext`, the representative server-selection and Setup URI Svelte dialogues mount and close through their normal controls, their mobile variants satisfy viewport, safe-area, and touch-target assertions, and the settings pane exposes only the effective deletion controls. These runtime checks complement the package tests without making Webapp maintenance the primary release gate.
The Community review lint completes with no errors. Advisory warnings remain in pre-existing UI sentence casing, deprecated Obsidian interfaces, unchecked dynamic setting data, and application-specific console output, especially in the Webapp. They are separate follow-up work and do not justify widening the package boundary or exposing additional compatibility paths.
The Community directory scanner preview completes with no source-code errors. The former findings attributed to generated `_types`, raw `src/lib` source, Node built-ins, forbidden rule suppressions, unsupported Obsidian APIs, and undescribed directive comments are absent. This confirms that the registry dependency is recognised as a package boundary. The preview identified behaviour-neutral redundant CLI candidate types and Webapp File System Access API assertions; these are corrected in the host source rather than carried as known warnings.
The remaining findings belong to application code. Browser-app DOM creation and inline styling require a framework-neutral browser presentation boundary rather than Obsidian-only helpers. Direct diagnostic output was nevertheless resolved at its existing ownership boundaries: Webapp components use an injected log function backed by `BrowserAPIService`, WebPeer retains output in its Svelte log store, Obsidian modules use the established Logger path, and duplicate console emission was removed from `ModuleLog`. The later Webapp and WebPeer recomposition around maintained Context and serviceFeature APIs should preserve these explicit output paths. Declarative settings adoption and deprecated Obsidian interfaces still require separate, focused changes.
The dependency preview also reports `uuid`, but the installed and locked graph resolves PouchDB's UUID dependency to the patched `uuid@11.1.1` through the repository override, and `npm audit` does not report the UUID advisory. The scanner appears to infer the older declared PouchDB range rather than the resolved override, so this warning is treated as a scanner false positive unless a packed-artefact inspection shows otherwise. A separate dependency review remains necessary for the pre-existing CLI `werift`/`ip` advisory, for which npm currently offers no automatic fix, and the development-only Istanbul `js-yaml` advisory.
The local real-Obsidian suite verifies the actual loaded `ObsidianServiceContext`, all 18 services, Vault reflection, CouchDB and Object Storage transfer, remote-activity accounting, CLI-to-Obsidian encrypted synchronisation, startup scanning, two-Vault create, update, delete, ordinary rename, case-only rename, target mismatch, Hidden File Sync, Customisation Sync, and setting Markdown export. These checks establish observable results and host composition rather than relying on declaration compatibility alone.
@@ -263,7 +267,7 @@ The current browser Harness has a pre-existing settings-migration initialisation
## Scanner and Repository Consequences
Consuming the npm package is expected to remove the common-library source and generated `_types` from the plug-in repository's community scan, because package dependencies are treated as dependencies rather than maintained plug-in source. The proof removes the old integration, but this scanner expectation must still be verified before the decision is accepted and released.
Consuming the npm package removes the common-library source and generated `_types` from the plug-in repository's Community directory scan, because package dependencies are treated as dependencies rather than maintained plug-in source. The branch preview confirms this expected boundary.
The change does not hide or resolve warnings in Self-hosted LiveSync-owned source. The scanner will continue to inspect the plug-in, CLI, Webapp, and WebPeer while those applications remain in the repository. Moving those applications to a LiveSync-family application repository may be considered after the package boundary is stable, but it is not part of this decision because the CLI and Webapp still import shared plug-in composition code.
@@ -303,14 +307,14 @@ The migration is complete only when:
- Self-hosted LiveSync no longer has `src/lib`, `_types`, or `@lib/*` source aliases;
- the CLI, Webapp, WebPeer, plug-in build, unit tests, integration tests, and focused real-Obsidian E2E pass against the reviewed package artefact;
- common-library downstream CI records and tests the exact Self-hosted LiveSync ref;
- a community scanner preview confirms the expected change in source findings; and
- a Community directory scanner preview confirms the expected change in source findings; and
- an external consumer can replace its submodule or custom loader with the documented package API.
## Consequences
- The common library gains an independently consumable and testable artefact while retaining its domain ownership and history.
- Self-hosted LiveSync release archives no longer need generated fallback declarations for an absent submodule.
- Community scanning can distinguish plug-in source from a reviewed dependency, subject to preview verification.
- Community directory scanning distinguishes plug-in source from the reviewed dependency.
- Changes which span the package and plug-in require coordinated package and downstream validation rather than one atomic source commit.
- Temporary compatibility exports increase the first package surface, but their pre-1.0 status and explicit classification prevent them from becoming silent permanent contracts.
- Translation, Svelte, and platform dependencies become optional composition concerns rather than root-package side effects.
+2 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import type { MenuItem } from "@/apps/browser/BrowserMenu";
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
type Props = {
item: MenuItem;
@@ -13,7 +14,7 @@
item.handler?.();
}
} catch (ex) {
console.error(ex);
Logger(ex, LOG_LEVEL_VERBOSE, "browser-menu");
}
closeMenu();
}
+5 -5
View File
@@ -13,10 +13,10 @@ type CLIP2PPeer = {
};
type CandidateSummary = {
id: string | "unknown";
candidateType: string | "unknown";
protocol: string | "unknown";
relayProtocol: string | "unknown";
id: string;
candidateType: string;
protocol: string;
relayProtocol: string;
};
function delay(ms: number): Promise<void> {
@@ -98,7 +98,7 @@ function getReportValue<T extends string | number>(
return typeof value === "string" || typeof value === "number" ? (value as T) : "unknown";
}
function summariseCandidate(reports: unknown[], candidateId: string | "unknown"): CandidateSummary | undefined {
function summariseCandidate(reports: unknown[], candidateId: string): CandidateSummary | undefined {
if (candidateId === "unknown") {
return undefined;
}
+4
View File
@@ -0,0 +1,4 @@
import type { LOG_LEVEL } from "@vrtmrz/livesync-commonlib/compat/common/types";
/** Diagnostic output supplied by the Webapp host. */
export type WebAppLog = (message: unknown, level: LOG_LEVEL, key?: string) => void;
@@ -1,4 +1,4 @@
import type { FilePath, UXStat } from "@vrtmrz/livesync-commonlib/compat/common/types";
import { LOG_LEVEL_NOTICE, type FilePath, type UXStat } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { IFileSystemAdapter } from "@vrtmrz/livesync-commonlib/compat/serviceModules/adapters";
import { FSAPIPathAdapter } from "./FSAPIPathAdapter";
import { FSAPITypeGuardAdapter } from "./FSAPITypeGuardAdapter";
@@ -7,6 +7,7 @@ import { FileSystemAccessStorageAdapter } from "@vrtmrz/livesync-commonlib/brows
import { FSAPIVaultAdapter } from "./FSAPIVaultAdapter";
import type { FSAPIFile, FSAPIFolder, FSAPIStat } from "./FSAPITypes";
import { shareRunningResult } from "octagonal-wheels/concurrency/lock_v2";
import type { WebAppLog } from "../WebAppLog";
/**
* Complete file system adapter implementation for FileSystem API
@@ -21,7 +22,10 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter<FSAPIFile, FSA
private fileCache = new Map<string, FSAPIFile>();
private handleCache = new Map<string, FileSystemFileHandle>();
constructor(private rootHandle: FileSystemDirectoryHandle) {
constructor(
private rootHandle: FileSystemDirectoryHandle,
private readonly addLog: WebAppLog
) {
this.path = new FSAPIPathAdapter();
this.typeGuard = new FSAPITypeGuardAdapter();
this.conversion = new FSAPIConversionAdapter();
@@ -30,14 +34,14 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter<FSAPIFile, FSA
}
private normalisePath(path: FilePath | string): string {
return this.path.normalisePath(path as string);
return this.path.normalisePath(path);
}
/**
* Get file handle for a given path
*/
private async getFileHandleByPath(p: FilePath | string): Promise<FileSystemFileHandle | null> {
const pathStr = p as string;
const pathStr = p;
// Check cache first
const cached = this.handleCache.get(pathStr);
@@ -213,7 +217,11 @@ export class FSAPIFileSystemAdapter implements IFileSystemAdapter<FSAPIFile, FSA
}
}
} catch (error) {
console.error(`Error scanning directory ${relativePath}:`, error);
this.addLog(
`Error scanning directory '${relativePath || "."}': ${String(error)}`,
LOG_LEVEL_NOTICE,
"fsapi-scan"
);
}
});
}
@@ -1,8 +1,9 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { storageAdapterContractCases } from "@/apps/_test/storageAdapterContract";
import { FSAPIFileSystemAdapter } from "./FSAPIFileSystemAdapter";
import { FileSystemAccessStorageAdapter } from "@vrtmrz/livesync-commonlib/browser";
import { FSAPIVaultAdapter } from "./FSAPIVaultAdapter";
import { LOG_LEVEL_NOTICE } from "@vrtmrz/livesync-commonlib/compat/common/types";
class MemoryFileHandle {
readonly kind = "file";
@@ -146,11 +147,30 @@ describe("FSAPIFileSystemAdapter path case", () => {
const root = memoryRoot as unknown as FileSystemDirectoryHandle;
const vault = new FSAPIVaultAdapter(root);
await vault.create("Calculus.md", "content");
const adapter = new FSAPIFileSystemAdapter(root);
const adapter = new FSAPIFileSystemAdapter(root, vi.fn());
await expect(adapter.getAbstractFileByPath("calculus.md")).resolves.toBeNull();
await expect(adapter.getAbstractFileByPathInsensitive("calculus.md")).resolves.toEqual(
expect.objectContaining({ path: "Calculus.md" })
);
});
it("reports scan failures through the injected Webapp log", async () => {
const root = {
name: "root",
async *entries(): AsyncIterableIterator<[string, FileSystemHandle]> {
throw new Error("scan failed");
},
} as unknown as FileSystemDirectoryHandle;
const addLog = vi.fn();
const adapter = new FSAPIFileSystemAdapter(root, addLog);
await adapter.scanDirectory();
expect(addLog).toHaveBeenCalledWith(
"Error scanning directory '.': Error: scan failed",
LOG_LEVEL_NOTICE,
"fsapi-scan"
);
});
});
-3
View File
@@ -101,7 +101,6 @@ async function startWithHistory(item: VaultHistoryItem): Promise<void> {
const handle = await historyStore.activateHistoryItem(item);
await startWithHandle(handle);
} catch (error) {
console.error("[Directory] Failed to open history vault:", error);
setStatus("error", `Failed to open saved vault: ${String(error)}`);
setBusyState(false);
}
@@ -113,7 +112,6 @@ async function startWithNewPicker(): Promise<void> {
const handle = await historyStore.pickNewVault();
await startWithHandle(handle);
} catch (error) {
console.error("[Directory] Failed to pick vault:", error);
setStatus("warning", `Vault selection was cancelled or failed: ${String(error)}`);
setBusyState(false);
}
@@ -132,7 +130,6 @@ async function initializeVaultSelector(): Promise<void> {
compatGlobal.addEventListener("load", () => {
initializeVaultSelector().catch((error) => {
console.error("Failed to initialize vault selector:", error);
setStatus("error", `Initialization failed: ${String(error)}`);
});
});
+37 -34
View File
@@ -7,7 +7,13 @@ import type { BrowserServiceHub } from "@vrtmrz/livesync-commonlib/compat/servic
import { LiveSyncBaseCore } from "@/LiveSyncBaseCore";
import { ServiceContext } from "@vrtmrz/livesync-commonlib/compat/services/base/ServiceBase";
import { initialiseServiceModulesFSAPI, type FSAPIServiceModules } from "./serviceModules/FSAPIServiceModules";
import type { ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";
import {
LOG_LEVEL_INFO,
LOG_LEVEL_NOTICE,
LOG_LEVEL_VERBOSE,
type LOG_LEVEL,
type ObsidianLiveSyncSettings,
} from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { BrowserAPIService } from "@vrtmrz/livesync-commonlib/compat/services/implements/browser/BrowserAPIService";
import type { InjectableSettingService } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableSettingService";
import { useOfflineScanner } from "@vrtmrz/livesync-commonlib/compat/serviceFeatures/offlineScanner";
@@ -59,14 +65,16 @@ class LiveSyncWebApp {
this.rootHandle = rootHandle;
}
private addLog(message: unknown, level: LOG_LEVEL = LOG_LEVEL_INFO, key?: string): void {
this.serviceHub?.API.addLog(message, level, key);
}
async initialize() {
console.log("Self-hosted LiveSync WebApp");
console.log("Initializing...");
console.log(`Vault directory: ${this.rootHandle.name}`);
// Create service context and hub
this.serviceHub = createLiveSyncBrowserServiceHub<ServiceContext>();
this.addLog("Self-hosted LiveSync WebApp", LOG_LEVEL_INFO, "initialise");
this.addLog("Initialising...", LOG_LEVEL_VERBOSE, "initialise");
this.addLog(`Vault directory: ${this.rootHandle.name}`, LOG_LEVEL_VERBOSE, "initialise");
// Setup API service
(this.serviceHub.API as BrowserAPIService<ServiceContext>).getSystemVaultName.setHandler(
@@ -79,9 +87,9 @@ class LiveSyncWebApp {
settingService.saveData.setHandler(async (data: ObsidianLiveSyncSettings) => {
try {
await this.saveSettingsToFile(data);
console.log("[Settings] Saved to .livesync/settings.json");
this.addLog("Saved to .livesync/settings.json", LOG_LEVEL_VERBOSE, "settings");
} catch (error) {
console.error("[Settings] Failed to save:", error);
this.addLog(`Failed to save settings: ${String(error)}`, LOG_LEVEL_NOTICE, "settings");
}
});
@@ -89,11 +97,11 @@ class LiveSyncWebApp {
try {
const data = await this.loadSettingsFromFile();
if (data) {
console.log("[Settings] Loaded from .livesync/settings.json");
this.addLog("Loaded from .livesync/settings.json", LOG_LEVEL_VERBOSE, "settings");
return { ...DEFAULT_SETTINGS, ...data } as ObsidianLiveSyncSettings;
}
} catch {
console.log("[Settings] Failed to load, using defaults");
this.addLog("Failed to load settings; using defaults", LOG_LEVEL_NOTICE, "settings");
}
return DEFAULT_SETTINGS as ObsidianLiveSyncSettings;
});
@@ -101,7 +109,7 @@ class LiveSyncWebApp {
// App lifecycle handlers
this.serviceHub.appLifecycle.scheduleRestart.setHandler(() => {
void (async () => {
console.log("[AppLifecycle] Restart requested");
this.addLog("Restart requested", LOG_LEVEL_INFO, "app-lifecycle");
await this.shutdown();
await this.initialize();
compatGlobal.setTimeout(() => {
@@ -152,19 +160,14 @@ class LiveSyncWebApp {
}
private async saveSettingsToFile(data: ObsidianLiveSyncSettings): Promise<void> {
try {
// Create .livesync directory if it doesn't exist
const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR, { create: true });
// Create .livesync directory if it does not exist
const livesyncDir = await this.rootHandle.getDirectoryHandle(SETTINGS_DIR, { create: true });
// Create/overwrite settings.json
const fileHandle = await livesyncDir.getFileHandle(SETTINGS_FILE, { create: true });
const writable = await fileHandle.createWritable();
await writable.write(JSON.stringify(data, null, 2));
await writable.close();
} catch (error) {
console.error("[Settings] Error saving to file:", error);
throw error;
}
// Create/overwrite settings.json
const fileHandle = await livesyncDir.getFileHandle(SETTINGS_FILE, { create: true });
const writable = await fileHandle.createWritable();
await writable.write(JSON.stringify(data, null, 2));
await writable.close();
}
private async loadSettingsFromFile(): Promise<Partial<ObsidianLiveSyncSettings> | null> {
@@ -186,47 +189,47 @@ class LiveSyncWebApp {
}
try {
console.log("[Starting] Initializing LiveSync...");
this.addLog("Initialising LiveSync...", LOG_LEVEL_INFO, "start");
const loadResult = await this.core.services.control.onLoad();
if (!loadResult) {
console.error("[Error] Failed to initialize LiveSync");
this.addLog("Failed to initialise LiveSync", LOG_LEVEL_NOTICE, "start");
this.showError("Failed to initialize LiveSync");
return;
}
await this.core.services.control.onReady();
console.log("[Ready] LiveSync is running");
this.addLog("LiveSync is running", LOG_LEVEL_INFO, "ready");
// Check if configured
const settings = this.core.services.setting.currentSettings();
if (!settings.isConfigured) {
console.warn("[Warning] LiveSync is not configured yet");
this.addLog("LiveSync is not configured yet", LOG_LEVEL_NOTICE, "configuration");
this.showWarning("Please configure CouchDB connection in settings");
} else {
console.log("[Info] LiveSync is configured and ready");
console.log(`[Info] Database: ${settings.couchDB_URI}/${settings.couchDB_DBNAME}`);
this.addLog("LiveSync is configured and ready", LOG_LEVEL_INFO, "configuration");
this.addLog(`Database: ${settings.couchDB_DBNAME}`, LOG_LEVEL_VERBOSE, "configuration");
this.showSuccess("LiveSync is ready!");
}
// Scan the directory to populate file cache
const fileAccess = this.platformServiceModules?.vaultAccess;
if (fileAccess) {
console.log("[Scanning] Scanning vault directory...");
this.addLog("Scanning vault directory...", LOG_LEVEL_VERBOSE, "scan");
await fileAccess.fsapiAdapter.scanDirectory();
const files = await fileAccess.fsapiAdapter.getFiles();
console.log(`[Scanning] Found ${files.length} files`);
this.addLog(`Found ${files.length} files`, LOG_LEVEL_VERBOSE, "scan");
}
} catch (error) {
console.error("[Error] Failed to start:", error);
this.addLog(`Failed to start: ${String(error)}`, LOG_LEVEL_NOTICE, "start");
this.showError(`Failed to start: ${error}`);
}
}
async shutdown() {
if (this.core) {
console.log("[Shutdown] Shutting down...");
this.addLog("Shutting down...", LOG_LEVEL_INFO, "shutdown");
// Stop file watching
const storageEventManager = this.platformServiceModules?.storageEventManager;
@@ -236,7 +239,7 @@ class LiveSyncWebApp {
await this.core.services.control.onUnload();
this.platformServiceModules = null;
console.log("[Shutdown] Complete");
this.addLog("Shutdown complete", LOG_LEVEL_INFO, "shutdown");
}
}
@@ -12,6 +12,8 @@ import type {
import type { FileEventItemSentinel } from "@vrtmrz/livesync-commonlib/compat/managers/StorageEventManager";
import type { FSAPIFile, FSAPIFolder } from "@/apps/webapp/adapters/FSAPITypes";
import { compatGlobal } from "@vrtmrz/livesync-commonlib/compat/common/coreEnvFunctions";
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "@vrtmrz/livesync-commonlib/compat/common/types";
import type { WebAppLog } from "../WebAppLog";
type FileSystemObserverRecord = {
changedHandle?: FileSystemFileHandle | FileSystemDirectoryHandle;
@@ -66,6 +68,8 @@ class FSAPIPersistenceAdapter implements IStorageEventPersistenceAdapter {
private storeName = "snapshots";
private snapshotKey = "file-events";
constructor(private readonly addLog: WebAppLog) {}
private async openDB(): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1);
@@ -96,7 +100,7 @@ class FSAPIPersistenceAdapter implements IStorageEventPersistenceAdapter {
db.close();
} catch (error) {
console.error("Failed to save snapshot:", error);
this.addLog(`Failed to save snapshot: ${String(error)}`, LOG_LEVEL_NOTICE, "fsapi-snapshot");
}
}
@@ -128,12 +132,16 @@ class FSAPIStatusAdapter implements IStorageEventStatusAdapter {
private lastUpdate = 0;
private updateInterval = 5000; // Update every 5 seconds
constructor(private readonly addLog: WebAppLog) {}
updateStatus(status: { batched: number; processing: number; totalQueued: number }): void {
const now = Date.now();
if (now - this.lastUpdate > this.updateInterval) {
if (status.totalQueued > 0 || status.processing > 0) {
console.log(
`[StorageEventManager] Batched: ${status.batched}, Processing: ${status.processing}, Total Queued: ${status.totalQueued}`
this.addLog(
`Batched: ${status.batched}, Processing: ${status.processing}, Total queued: ${status.totalQueued}`,
LOG_LEVEL_VERBOSE,
"storage-events"
);
}
this.lastUpdate = now;
@@ -177,14 +185,17 @@ class FSAPIConverterAdapter implements IStorageEventConverterAdapter<FSAPIFile>
class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
private observer: FileSystemObserverInstance | null = null;
constructor(private rootHandle: FileSystemDirectoryHandle) {}
constructor(
private rootHandle: FileSystemDirectoryHandle,
private readonly addLog: WebAppLog
) {}
async beginWatch(handlers: IStorageEventWatchHandlers): Promise<void> {
// Use FileSystemObserver if available (Chrome 124+)
const FileSystemObserver = (compatGlobal as GlobalWithFileSystemObserver).FileSystemObserver;
if (!FileSystemObserver) {
console.log("[FSAPIWatchAdapter] FileSystemObserver not available, file watching disabled");
console.log("[FSAPIWatchAdapter] Consider using Chrome 124+ for real-time file watching");
this.addLog("FileSystemObserver is not available; file watching is disabled", LOG_LEVEL_INFO, "fsapi-watch");
this.addLog("Chrome 124 or later supports real-time file watching", LOG_LEVEL_INFO, "fsapi-watch");
return Promise.resolve();
}
@@ -203,7 +214,7 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
continue;
}
console.log(`[FileSystemObserver] ${type}: ${relativePath}`);
this.addLog(`${type}: ${relativePath}`, LOG_LEVEL_VERBOSE, "filesystem-observer");
// Convert to our event handlers
try {
@@ -259,9 +270,10 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
}
}
} catch (error) {
console.error(
`[FileSystemObserver] Error processing ${type} event for ${relativePath}:`,
error
this.addLog(
`Error processing ${type} event for ${relativePath}: ${String(error)}`,
LOG_LEVEL_NOTICE,
"filesystem-observer"
);
}
}
@@ -269,10 +281,10 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
// Start observing
await this.observer.observe(this.rootHandle, { recursive: true });
console.log("[FSAPIWatchAdapter] FileSystemObserver started successfully");
this.addLog("FileSystemObserver started successfully", LOG_LEVEL_INFO, "fsapi-watch");
} catch (error) {
console.error("[FSAPIWatchAdapter] Failed to start FileSystemObserver:", error);
console.log("[FSAPIWatchAdapter] Falling back to manual sync mode");
this.addLog(`Failed to start FileSystemObserver: ${String(error)}`, LOG_LEVEL_NOTICE, "fsapi-watch");
this.addLog("Falling back to manual sync mode", LOG_LEVEL_INFO, "fsapi-watch");
}
return Promise.resolve();
@@ -283,9 +295,9 @@ class FSAPIWatchAdapter implements IStorageEventWatchAdapter {
try {
this.observer.disconnect();
this.observer = null;
console.log("[FSAPIWatchAdapter] FileSystemObserver stopped");
this.addLog("FileSystemObserver stopped", LOG_LEVEL_INFO, "fsapi-watch");
} catch (error) {
console.error("[FSAPIWatchAdapter] Error stopping observer:", error);
this.addLog(`Error stopping observer: ${String(error)}`, LOG_LEVEL_NOTICE, "fsapi-watch");
}
}
}
@@ -301,11 +313,11 @@ export class FSAPIStorageEventManagerAdapter implements IStorageEventManagerAdap
readonly status: FSAPIStatusAdapter;
readonly converter: FSAPIConverterAdapter;
constructor(rootHandle: FileSystemDirectoryHandle) {
constructor(rootHandle: FileSystemDirectoryHandle, addLog: WebAppLog) {
this.typeGuard = new FSAPITypeGuardAdapter();
this.persistence = new FSAPIPersistenceAdapter();
this.watch = new FSAPIWatchAdapter(rootHandle);
this.status = new FSAPIStatusAdapter();
this.persistence = new FSAPIPersistenceAdapter(addLog);
this.watch = new FSAPIWatchAdapter(rootHandle, addLog);
this.status = new FSAPIStatusAdapter(addLog);
this.converter = new FSAPIConverterAdapter();
}
}
@@ -12,7 +12,9 @@ export class StorageEventManagerFSAPI extends StorageEventManagerBase<FSAPIStora
core: LiveSyncBaseCore<ServiceContext, IMinimumLiveSyncCommands>,
dependencies: StorageEventManagerBaseDependencies
) {
const adapter = new FSAPIStorageEventManagerAdapter(rootHandle);
const adapter = new FSAPIStorageEventManagerAdapter(rootHandle, (message, level, key) =>
dependencies.APIService.addLog(message, level, key)
);
super(adapter, dependencies);
this.fsapiAdapter = adapter;
this.core = core;
@@ -7,7 +7,9 @@ import { FSAPIFileSystemAdapter } from "@/apps/webapp/adapters/FSAPIFileSystemAd
*/
export class FileAccessFSAPI extends FileAccessBase<FSAPIFileSystemAdapter> {
constructor(rootHandle: FileSystemDirectoryHandle, dependencies: FileAccessBaseDependencies) {
const adapter = new FSAPIFileSystemAdapter(rootHandle);
const adapter = new FSAPIFileSystemAdapter(rootHandle, (message, level, key) =>
dependencies.APIService.addLog(message, level, key)
);
super(adapter, dependencies);
}
-1
View File
@@ -9,7 +9,6 @@ let _logs = [] as string[];
const maxLines = 10000;
setGlobalLogFunction((msg, level) => {
console.log(msg);
const msgstr = typeof msg === "string" ? msg : JSON.stringify(msg);
const strLog = `${new Date().toISOString()}\u2001${msgstr}`;
_logs.push(strLog);
+1 -5
View File
@@ -23,16 +23,13 @@
.addItem((item) => item.setTitle("📤 Only Send").onClick(() => {}))
.addSeparator()
.addItem((item) => {
item.setTitle("🔧 Get Configuration").onClick(async () => {
console.log("Get Configuration");
});
item.setTitle("🔧 Get Configuration").onClick(async () => {});
})
.addSeparator()
.addItem((item) => {
const mark = "checkmark";
item.setTitle("Toggle Sync on connect")
.onClick(async () => {
console.log("Toggle Sync on connect");
// await this.toggleProp(peer, "syncOnConnect");
})
.setIcon(mark);
@@ -41,7 +38,6 @@
const mark = null;
item.setTitle("Toggle Watch on connect")
.onClick(async () => {
console.log("Toggle Watch on connect");
// await this.toggleProp(peer, "watchOnConnect");
})
.setIcon(mark);
+1 -1
View File
@@ -91,7 +91,7 @@ export const _OpenKeyValueDatabase = async (dbKey: string): Promise<KeyValueData
// await closeDB();
await deleteDB(dbKey, {
blocked() {
console.warn(`Database delete blocked for ${dbKey}`);
Logger(`Database delete blocked for ${dbKey}`);
},
});
},
+6 -5
View File
@@ -74,12 +74,13 @@ export class ModuleDev extends AbstractObsidianModule {
if (w) {
const id = await this.services.path.path2id(filename as FilePathWithPrefix);
const f = await this.core.localDatabase.getRaw(id);
console.log(f);
console.log(f._rev);
this._log(f, LOG_LEVEL_VERBOSE);
this._log(f._rev, LOG_LEVEL_VERBOSE);
const revConflict = f._rev.split("-")[0] + "-" + (parseInt(f._rev.split("-")[1]) + 1).toString();
console.log(await this.core.localDatabase.bulkDocsRaw([f], { new_edits: false }));
console.log(
await this.core.localDatabase.bulkDocsRaw([{ ...f, _rev: revConflict }], { new_edits: false })
this._log(await this.core.localDatabase.bulkDocsRaw([f], { new_edits: false }), LOG_LEVEL_VERBOSE);
this._log(
await this.core.localDatabase.bulkDocsRaw([{ ...f, _rev: revConflict }], { new_edits: false }),
LOG_LEVEL_VERBOSE
);
}
},
@@ -657,7 +657,6 @@ export class DocumentHistoryModal extends Modal {
const { contentEl } = this;
contentEl.empty();
this.BlobURLs.forEach((value) => {
console.log(value);
if (value) URL.revokeObjectURL(value);
});
}
-7
View File
@@ -551,13 +551,6 @@ ${stringifyYaml(info)}
return;
}
addDisplayLog(newMessage);
if (message instanceof Error) {
console.error(vaultName + ":" + newMessage);
} else if (level >= LOG_LEVEL_INFO) {
console.log(vaultName + ":" + newMessage);
} else {
console.debug(vaultName + ":" + newMessage);
}
if (!this.settings?.showOnlyIconsOnEditor) {
this.statusLog.value = messageContent;
}
@@ -121,8 +121,7 @@ export async function migrateDatabases(operationName: string, from: PouchDB.Data
Logger(`Destroyed existing destination database for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
const dbTo2 = await openTo();
const info2 = await dbTo2.info(); // ensure created
console.log(info2);
await dbTo2.info(); // ensure created
Logger(`Re-created destination database for migration: ${operationName}.`, LOG_LEVEL_NOTICE, "migration");
const info = await from.info();
@@ -6,6 +6,7 @@
import Decision from "@/modules/services/LiveSyncUI/components/Decision.svelte";
import UserDecisions from "@/modules/services/LiveSyncUI/components/UserDecisions.svelte";
import { checkConfig, type ConfigCheckResult, type ResultError, type ResultErrorMessage } from "./utilCheckCouchDB";
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
type Props = {
trialRemoteSetting: ObsidianLiveSyncSettings;
};
@@ -15,10 +16,9 @@
detectedIssues = [];
try {
const fixResults = await checkConfig(trialRemoteSetting);
console.dir(fixResults);
detectedIssues = fixResults;
} catch (e) {
console.error("Error during testAndFixSettings:", e);
Logger(e, LOG_LEVEL_VERBOSE, "setup-couchdb-check");
detectedIssues.push({ message: `Error during testAndFixSettings: ${e}`, result: "error", classes: [] });
}
}
@@ -37,7 +37,7 @@
processing = true;
await issue.fix();
} catch (e) {
console.error("Error during fixIssue:", e);
Logger(e, LOG_LEVEL_VERBOSE, "setup-couchdb-fix");
}
await testAndFixSettings();
processing = false;
@@ -27,6 +27,7 @@
import { SETTING_KEY_P2P_DEVICE_NAME } from "@vrtmrz/livesync-commonlib/compat/common/types";
import ExtraItems from "@/modules/services/LiveSyncUI/components/ExtraItems.svelte";
import { TYPE_CANCELLED, type SetupRemoteP2PResultType } from "./setupDialogTypes";
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
const default_setting = pickP2PSyncSettings(DEFAULT_SETTINGS);
let syncSetting = $state<P2PConnectionInfo>({ ...default_setting });
@@ -137,7 +138,7 @@
replicator.close();
dummyPouch.destroy();
} catch (e) {
console.error(e);
Logger(e, LOG_LEVEL_VERBOSE, "setup-p2p-cleanup");
}
}
} finally {
@@ -2,13 +2,14 @@ import { getLanguage, requireApiVersion } from "@/deps";
import { createServiceFeature } from "@vrtmrz/livesync-commonlib/compat/interfaces/ServiceModule";
import { SUPPORTED_I18N_LANGS, type I18N_LANGS } from "@vrtmrz/livesync-commonlib/compat/common/rosetta";
import { $msg, __onMissingTranslation, setLang } from "@vrtmrz/livesync-commonlib/compat/common/i18n";
import { LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
function tryGetLanguage() {
function tryGetLanguage(onError: (error: unknown) => void) {
if (requireApiVersion("1.8.7")) {
try {
return getLanguage();
} catch (e) {
console.error("Failed to get Obsidian language, defaulting to 'en'", e);
onError(e);
}
}
return "en";
@@ -20,7 +21,13 @@ export const enableI18nFeature = createServiceFeature(async ({ services: { setti
let isChanged = false;
const settings = setting.currentSettings();
if (settings.displayLanguage == "") {
const obsidianLanguage = tryGetLanguage();
const obsidianLanguage = tryGetLanguage((error) => {
API.addLog(
`Failed to get Obsidian language; defaulting to 'en': ${String(error)}`,
LOG_LEVEL_VERBOSE,
"i18n-language"
);
});
if (
SUPPORTED_I18N_LANGS.indexOf(obsidianLanguage) !== -1 && // Check if the language is supported
obsidianLanguage != settings.displayLanguage // Check if the language is different from the current setting
+1
View File
@@ -25,6 +25,7 @@ This will call for your help once again. I would be very grateful for your co-op
- Replaced the embedded Commonlib source and generated fallback declarations with a locked compiled package, reducing duplicated release and repository-scanner inputs without changing synchronisation behaviour.
- Moved CLI standard input, prompting, and protocol output behind a host-injected Commonlib contract, and routed adapter diagnostics through the service logging API, without changing command output formats.
- Routed Webapp, WebPeer, and plug-in diagnostics through their application-owned log paths instead of writing directly to the browser console.
### Testing