Deep layout system implementation knowledge: ILayoutStorage contracts, IndexedDB schema, sync operation computation, mutex-locked LayoutManager, conflict resolution, WriteThroughLayoutCache, NamespacedLayoutStorage, and CurrentLayoutProvider reducers.
69%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.github/skills/layouts-internals/SKILL.mdDefined in packages/suite-base/src/services/ILayoutStorage.ts. Every CRUD method takes a
namespace argument — there are no sync-time methods like getLastSyncTime.
export interface ILayoutStorage {
list(namespace: string): Promise<readonly Layout[]>;
get(namespace: string, id: LayoutID): Promise<Layout | undefined>;
put(namespace: string, layout: Layout): Promise<Layout>;
delete(namespace: string, id: LayoutID): Promise<void>;
// Optional one-time migration of pre-namespace local layouts
migrateUnnamespacedLayouts?(namespace: string): Promise<void>;
// Convert local layouts to personal layouts on login
importLayouts(params: { fromNamespace: string; toNamespace: string }): Promise<void>;
}The Layout type tracks baseline (last explicit save), working (unsaved edits, or undefined),
and syncInfo (remote status). LayoutSyncStatus is
"new" | "updated" | "tracked" | "locally-deleted" | "remotely-deleted".
packages/suite-base/src/IdbLayoutStorage.ts:
// DB name: `${KEY_WORKSPACE_PREFIX}lichtblick-layouts` (version 1)
// Object store: "layouts"
// keyPath: ["namespace", "layout.id"] (composite primary key)
// index "namespace": keyPath "namespace" (non-unique)
interface LayoutsDB extends DBSchema {
layouts: {
key: [namespace: string, id: LayoutID];
value: { namespace: string; layout: Layout };
indexes: { namespace: string };
};
}list() uses getAllFromIndex("layouts", "namespace", namespace)migrateLayout() before returning{ namespace, layout } — the primary key reaches into
layout.id via the "layout.id" keyPath segmentpackages/suite-base/src/services/LayoutManager/NamespacedLayoutStorage.ts — wraps an
ILayoutStorage and binds a namespace so callers omit it. It is not itself an ILayoutStorage
(its methods drop the namespace argument). The constructor kicks off an async migration/import:
export class NamespacedLayoutStorage {
#migration: Promise<void>;
constructor(
private storage: ILayoutStorage,
private namespace: string,
opts: { migrateUnnamespacedLayouts: boolean; importFromNamespace: string | undefined },
) {
// runs migrateUnnamespacedLayouts?() and/or importLayouts() once
}
async list(): Promise<readonly Layout[]> { await this.#migration; return this.storage.list(this.namespace); }
async get(id: LayoutID) { await this.#migration; return this.storage.get(this.namespace, id); }
async put(layout: Layout) { await this.#migration; return this.storage.put(this.namespace, layout); }
async delete(id: LayoutID) { await this.#migration; return this.storage.delete(this.namespace, id); }
}packages/suite-base/src/services/LayoutManager/WriteThroughLayoutCache.ts — an ILayoutStorage
that calls the underlying list() once per namespace (via LazilyInitialized) and serves
subsequent reads from an in-memory Map, writing through to the inner storage on put/delete.
Assumes nothing else mutates the underlying storage.
export default class WriteThroughLayoutCache implements ILayoutStorage {
#cacheByNamespace = new Map<string, LazilyInitialized<Map<string, Layout>>>();
constructor(private storage: ILayoutStorage) {}
async put(namespace: string, layout: Layout): Promise<Layout> {
const result = await this.storage.put(namespace, layout);
(await this.#getOrCreateCache(namespace).get()).set(result.id, result);
return result;
}
// get/list/delete read/write the per-namespace cache map
}packages/suite-base/src/services/LayoutManager/LayoutManager.ts.
All local storage access is wrapped in a MutexLocked (from @lichtblick/den/async) so multi-step
operations are atomic. A single in-flight sync is tracked by currentSync?: Promise<void>.
class LayoutManager {
private local: MutexLocked<NamespacedLayoutStorage>;
private currentSync?: Promise<void>;
async getLayouts(): Promise<readonly Layout[]> {
return await this.local.runExclusive(async (local) => await local.list());
}
}⚠️
LayoutManagerdoes not implement exponential backoff / jitter /#baseInterval/#maxInterval. Sync scheduling (and any retry/online-trigger behavior) lives in the provider layer, not inLayoutManager. Do not assume a built-in backoff timer here.
packages/suite-base/src/services/LayoutManager/utils/computeLayoutSyncOperations.ts. The real
SyncOperation is a tagged union carrying a local boolean and the operation type:
export type SyncOperation =
| { local: true; type: "add-to-cache"; remoteLayout: RemoteLayout }
| { local: true; type: "delete-local"; localLayout: Layout }
| { local: true; type: "mark-deleted"; localLayout: Layout }
| { local: false; type: "delete-remote"; localLayout: Layout }
| { local: false; type: "upload-new"; localLayout: Layout }
| { local: false; type: "upload-updated"; localLayout: Layout }
| {
local: true;
type: "update-baseline";
localLayout: Layout & { syncInfo: NonNullable<Layout["syncInfo"]> };
remoteLayout: RemoteLayout;
};⚠️ There is no
"upload" | "download" | "conflict"union, nolayoutIdfield, and nosyncStatus/baseline.savedAtcomparison as shown previously. Operations are keyed bylocalLayout/remoteLayout, and thelocalflag indicates whether the op mutates local (cache) or remote storage.
The function iterates local layouts (matching against a remoteLayoutsById map) and then any
remaining remote-only layouts, pushing the appropriate operation for each.
packages/suite-base/src/providers/CurrentLayoutProvider/reducers.ts. The actual action type
values handled are:
CHANGE_PANEL_LAYOUT, SAVE_PANEL_CONFIGS, SAVE_FULL_PANEL_CONFIG, CREATE_TAB_PANEL,
OVERWRITE_GLOBAL_DATA, SET_GLOBAL_DATA, SET_USER_NODES, SET_PLAYBACK_CONFIG, CLOSE_PANEL,
SPLIT_PANEL, SWAP_PANEL, MOVE_TAB, ADD_PANEL, DROP_PANEL, START_DRAG, END_DRAG.
⚠️ There are no
REMOVE_PANEL,MOVE_PANEL, orUPDATE_PANEL_CONFIGactions. Panel removal isCLOSE_PANEL; config writes go throughSAVE_PANEL_CONFIGS/SAVE_FULL_PANEL_CONFIG.
ADD_PANEL — find insertion point → add new leaf to the mosaic tree.
CLOSE_PANEL — remove leaf → if parent collapses to a single child, hoist it.
SPLIT_PANEL:
Replace leaf with { direction, first: existingLeaf, second: newLeaf, splitPercentage: 50 }case "SAVE_PANEL_CONFIGS":
// merges each { id, config } entry into state's configById,
// optionally via a per-panel override functionpackages/suite-desktop/src/renderer/services/DesktopLayoutLoader.ts — namespace = "local". Reads
layouts from the desktop file system through the preload storageBridge (list / get / put /
delete), not a desktopBridge.fetchLayouts() call.
lichtblick-suite/lichtblick
9589f38
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.