@wildwinter/app-shell 0.24.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-store.cjs +9 -5
- package/dist/app-store.cjs.map +1 -1
- package/dist/app-store.d.cts +21 -4
- package/dist/app-store.d.ts +21 -4
- package/dist/app-store.js +9 -5
- package/dist/app-store.js.map +1 -1
- package/dist/index.cjs +14 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/vc-status.cjs +4 -0
- package/dist/vc-status.cjs.map +1 -1
- package/dist/vc-status.d.cts +19 -0
- package/dist/vc-status.d.ts +19 -0
- package/dist/vc-status.js +4 -0
- package/dist/vc-status.js.map +1 -1
- package/dist/vc.css +10 -0
- package/dist/vc.css.map +1 -1
- package/package.json +1 -1
package/dist/vc-status.cjs
CHANGED
|
@@ -23,6 +23,7 @@ async function query(shards) {
|
|
|
23
23
|
const states = /* @__PURE__ */ new Map();
|
|
24
24
|
for (const s of shards) states.set(s.key, { writable: true });
|
|
25
25
|
const keyOf = new Map(shards.map((s) => [s.path, s.key]));
|
|
26
|
+
const isPrimary = new Set(shards.filter((s) => s.primary).map((s) => s.path));
|
|
26
27
|
const doRemote = Date.now() - lastRemoteAt >= REMOTE_STATUS_THROTTLE_MS;
|
|
27
28
|
let system = "filesystem";
|
|
28
29
|
try {
|
|
@@ -32,6 +33,9 @@ async function query(shards) {
|
|
|
32
33
|
system = st.system;
|
|
33
34
|
const acc = states.get(key);
|
|
34
35
|
if (!st.writable) acc.writable = false;
|
|
36
|
+
if (st.openedByMe) acc.checkedOutByMe = true;
|
|
37
|
+
if (st.dirty) acc.dirty = true;
|
|
38
|
+
if (st.tracked === false && isPrimary.has(st.filePath)) acc.untracked = true;
|
|
35
39
|
if (doRemote) {
|
|
36
40
|
if (st.lockedBy?.length) acc.lockedBy = [.../* @__PURE__ */ new Set([...acc.lockedBy ?? [], ...st.lockedBy])];
|
|
37
41
|
if (st.outOfDate) acc.outOfDate = true;
|
package/dist/vc-status.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vc-status.ts"],"names":["fileStatusAsync"],"mappings":"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","file":"vc-status.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Per-shard version-control state, over @wildwinter/simple-vc-lib.\n//\n// The family's, because it follows from the SHAPE rather than from any app's\n// subject: these are all Electron apps editing JSON shards in a working copy,\n// so they all want to say \"locked by Bo\" before a save is refused rather than\n// after. Patterpad wrote it, Storyletter hand-ported it, and this is the third\n// copy becoming the only one.\n//\n// A SHARD here is just a key and a path: what the key means is the app's\n// business (a scene, a deck, a box's three files folded into one row).\n//\n// THROTTLING, which is the reason this is a module rather than a function call:\n// the local bits (`writable` - the on-disk read-only bit a lock VCS keys off)\n// are cheap and refresh on every call, so a save re-badges at once. The remote\n// bits (`lockedBy` / `outOfDate`) cost a SERVER round-trip under SVN and\n// Plastic, so they are re-queried at most once per window and otherwise reused\n// from the cache. The query is async (never blocks the main-process event loop)\n// and coalesced (a save, a focus and the poll timer arriving together make ONE\n// query), so a lock badge can never make typing stutter.\n//\n// Best-effort throughout: if the query throws (no tooling, broken repo) every\n// shard reports clean and writable, and the editor carries on.\n// ---------------------------------------------------------------------------\n\nimport { fileStatusAsync } from \"@wildwinter/simple-vc-lib\";\nimport type { VCFileStatus, VCStatusOptions } from \"@wildwinter/simple-vc-lib\";\n\n/** One shard to ask about: the key the renderer looks state up by, and its\n * absolute path on disk. */\nexport interface ShardRef {\n key: string;\n path: string;\n}\n\n/** One shard's version-control state. All-clean is `{ writable: true }`. */\nexport interface ShardState {\n /** Writable on disk right now. `false` = the read-only bit is set (a lock\n * VCS has not checked it out yet, or someone else holds it). */\n writable: boolean;\n /** Who else holds it open / locked (e.g. \"bo@bo-ws\") - we can read it, not\n * write it. */\n lockedBy?: string[];\n /** A newer revision exists on the server (get latest before editing). */\n outOfDate?: boolean;\n}\n\nexport interface ShardStatus {\n /** The backend simple-vc-lib actually detected (\"git\" / \"perforce\" / ...). */\n system: string;\n states: Map<string, ShardState>;\n}\n\n/** How long a remote (server round-trip) answer stays good for. */\nexport const REMOTE_STATUS_THROTTLE_MS = 15_000;\n\ntype StatusReader = (paths: string[], options?: VCStatusOptions) => Promise<VCFileStatus[]>;\n\nlet read: StatusReader = fileStatusAsync;\n/** What the app calls itself in the one warning this module can emit. */\nlet logPrefix = \"app-shell\";\n/** Name the app, for that warning. */\nexport function setVcLogPrefix(name: string): void { logPrefix = name; }\nlet lastRemoteAt = 0;\nlet cachedRemoteBits = new Map<string, { lockedBy?: string[]; outOfDate?: boolean }>();\nlet inFlight: Promise<ShardStatus> | undefined;\n\n/** Test seam, mirroring simple-vc-lib's own `setCommandRunner`: swap the status\n * reader for canned answers. Pass null to restore the real one. */\nexport function setStatusReader(reader: StatusReader | null): void {\n read = reader ?? fileStatusAsync;\n}\n\n/** Drop the throttle window and the cached remote bits - called when a project\n * opens, so a new project never inherits the last one's answers. */\nexport function resetShardStatus(): void {\n lastRemoteAt = 0;\n cachedRemoteBits = new Map();\n}\n\nasync function query(shards: ShardRef[]): Promise<ShardStatus> {\n const states = new Map<string, ShardState>();\n for (const s of shards) states.set(s.key, { writable: true });\n const keyOf = new Map(shards.map((s) => [s.path, s.key]));\n // Hit the server only when the window has elapsed; otherwise a cheap local read.\n const doRemote = Date.now() - lastRemoteAt >= REMOTE_STATUS_THROTTLE_MS;\n let system = \"filesystem\";\n try {\n for (const st of await read(shards.map((s) => s.path), { remote: doRemote })) {\n const key = keyOf.get(st.filePath);\n if (key === undefined) continue;\n system = st.system;\n const acc = states.get(key)!;\n if (!st.writable) acc.writable = false;\n if (doRemote) { // remote bits are authoritative only on a fresh server query\n if (st.lockedBy?.length) acc.lockedBy = [...new Set([...(acc.lockedBy ?? []), ...st.lockedBy])];\n if (st.outOfDate) acc.outOfDate = true;\n }\n }\n if (doRemote) { // snapshot the fresh remote bits for the throttled calls that follow\n lastRemoteAt = Date.now();\n cachedRemoteBits = new Map([...states].map(([key, s]) => [key, {\n ...(s.lockedBy !== undefined ? { lockedBy: s.lockedBy } : {}),\n ...(s.outOfDate !== undefined ? { outOfDate: s.outOfDate } : {}),\n }]));\n } else { // overlay the last known remote bits onto the fresh local snapshot\n for (const [key, acc] of states) {\n const cached = cachedRemoteBits.get(key);\n if (cached?.lockedBy?.length) acc.lockedBy = cached.lockedBy;\n if (cached?.outOfDate) acc.outOfDate = true;\n }\n }\n } catch (e) {\n // Tooling missing, repo error, timeout: everything reads clean + writable.\n console.warn(`${logPrefix}: the version-control status query failed - treating every shard as writable:`, e);\n }\n return { system, states };\n}\n\n/** The current state of every shard, throttled + coalesced as above. Never\n * throws: a failed query yields a clean, writable snapshot. */\nexport function shardStatus(shards: ShardRef[]): Promise<ShardStatus> {\n if (!inFlight) {\n inFlight = (async () => {\n try { return await query(shards); } finally { inFlight = undefined; }\n })();\n }\n return inFlight;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/vc-status.ts"],"names":["fileStatusAsync"],"mappings":"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","file":"vc-status.cjs","sourcesContent":["// ---------------------------------------------------------------------------\n// Per-shard version-control state, over @wildwinter/simple-vc-lib.\n//\n// The family's, because it follows from the SHAPE rather than from any app's\n// subject: these are all Electron apps editing JSON shards in a working copy,\n// so they all want to say \"locked by Bo\" before a save is refused rather than\n// after. Patterpad wrote it, Storyletter hand-ported it, and this is the third\n// copy becoming the only one.\n//\n// A SHARD here is just a key and a path: what the key means is the app's\n// business (a scene, a deck, a box's three files folded into one row).\n//\n// THROTTLING, which is the reason this is a module rather than a function call:\n// the local bits (`writable` - the on-disk read-only bit a lock VCS keys off)\n// are cheap and refresh on every call, so a save re-badges at once. The remote\n// bits (`lockedBy` / `outOfDate`) cost a SERVER round-trip under SVN and\n// Plastic, so they are re-queried at most once per window and otherwise reused\n// from the cache. The query is async (never blocks the main-process event loop)\n// and coalesced (a save, a focus and the poll timer arriving together make ONE\n// query), so a lock badge can never make typing stutter.\n//\n// Best-effort throughout: if the query throws (no tooling, broken repo) every\n// shard reports clean and writable, and the editor carries on.\n// ---------------------------------------------------------------------------\n\nimport { fileStatusAsync } from \"@wildwinter/simple-vc-lib\";\nimport type { VCFileStatus, VCStatusOptions } from \"@wildwinter/simple-vc-lib\";\n\n/** One shard to ask about: the key the renderer looks state up by, and its\n * absolute path on disk. */\nexport interface ShardRef {\n key: string;\n path: string;\n /**\n * This path is the one that decides whether the KEY is untracked.\n *\n * A key usually spans several files, and \"new\" is the one state that does not\n * fold by \"any of them counts\": a scene whose flow file is committed but whose\n * sidecar has never been written is not a new scene, it is an edited one.\n * Patterpad keys off the flow shard for exactly that reason. Mark the canonical\n * path and the rest are ignored for `untracked`; mark none and the key never\n * reports untracked.\n */\n primary?: boolean;\n}\n\n/** One shard's version-control state. All-clean is `{ writable: true }`. */\nexport interface ShardState {\n /** Writable on disk right now. `false` = the read-only bit is set (a lock\n * VCS has not checked it out yet, or someone else holds it). */\n writable: boolean;\n /** Who else holds it open / locked (e.g. \"bo@bo-ws\") - we can read it, not\n * write it. */\n lockedBy?: string[];\n /** A newer revision exists on the server (get latest before editing). */\n outOfDate?: boolean;\n /** WE hold it: checked out / opened by the current user, still ours to edit.\n * Distinct from `writable`, which says only that the bit is off. */\n checkedOutByMe?: boolean;\n /** Tracked, with local changes not yet committed. */\n dirty?: boolean;\n /** Not in version control yet. Decided by the `primary` path alone (see\n * `ShardRef.primary`). */\n untracked?: boolean;\n}\n\nexport interface ShardStatus {\n /** The backend simple-vc-lib actually detected (\"git\" / \"perforce\" / ...). */\n system: string;\n states: Map<string, ShardState>;\n}\n\n/** How long a remote (server round-trip) answer stays good for. */\nexport const REMOTE_STATUS_THROTTLE_MS = 15_000;\n\ntype StatusReader = (paths: string[], options?: VCStatusOptions) => Promise<VCFileStatus[]>;\n\nlet read: StatusReader = fileStatusAsync;\n/** What the app calls itself in the one warning this module can emit. */\nlet logPrefix = \"app-shell\";\n/** Name the app, for that warning. */\nexport function setVcLogPrefix(name: string): void { logPrefix = name; }\nlet lastRemoteAt = 0;\nlet cachedRemoteBits = new Map<string, { lockedBy?: string[]; outOfDate?: boolean }>();\nlet inFlight: Promise<ShardStatus> | undefined;\n\n/** Test seam, mirroring simple-vc-lib's own `setCommandRunner`: swap the status\n * reader for canned answers. Pass null to restore the real one. */\nexport function setStatusReader(reader: StatusReader | null): void {\n read = reader ?? fileStatusAsync;\n}\n\n/** Drop the throttle window and the cached remote bits - called when a project\n * opens, so a new project never inherits the last one's answers. */\nexport function resetShardStatus(): void {\n lastRemoteAt = 0;\n cachedRemoteBits = new Map();\n}\n\nasync function query(shards: ShardRef[]): Promise<ShardStatus> {\n const states = new Map<string, ShardState>();\n for (const s of shards) states.set(s.key, { writable: true });\n const keyOf = new Map(shards.map((s) => [s.path, s.key]));\n const isPrimary = new Set(shards.filter((s) => s.primary).map((s) => s.path));\n // Hit the server only when the window has elapsed; otherwise a cheap local read.\n const doRemote = Date.now() - lastRemoteAt >= REMOTE_STATUS_THROTTLE_MS;\n let system = \"filesystem\";\n try {\n for (const st of await read(shards.map((s) => s.path), { remote: doRemote })) {\n const key = keyOf.get(st.filePath);\n if (key === undefined) continue;\n system = st.system;\n const acc = states.get(key)!;\n if (!st.writable) acc.writable = false;\n // Local bits, cheap on every provider, so never throttled.\n if (st.openedByMe) acc.checkedOutByMe = true;\n if (st.dirty) acc.dirty = true;\n if (st.tracked === false && isPrimary.has(st.filePath)) acc.untracked = true;\n if (doRemote) { // remote bits are authoritative only on a fresh server query\n if (st.lockedBy?.length) acc.lockedBy = [...new Set([...(acc.lockedBy ?? []), ...st.lockedBy])];\n if (st.outOfDate) acc.outOfDate = true;\n }\n }\n if (doRemote) { // snapshot the fresh remote bits for the throttled calls that follow\n lastRemoteAt = Date.now();\n cachedRemoteBits = new Map([...states].map(([key, s]) => [key, {\n ...(s.lockedBy !== undefined ? { lockedBy: s.lockedBy } : {}),\n ...(s.outOfDate !== undefined ? { outOfDate: s.outOfDate } : {}),\n }]));\n } else { // overlay the last known remote bits onto the fresh local snapshot\n for (const [key, acc] of states) {\n const cached = cachedRemoteBits.get(key);\n if (cached?.lockedBy?.length) acc.lockedBy = cached.lockedBy;\n if (cached?.outOfDate) acc.outOfDate = true;\n }\n }\n } catch (e) {\n // Tooling missing, repo error, timeout: everything reads clean + writable.\n console.warn(`${logPrefix}: the version-control status query failed - treating every shard as writable:`, e);\n }\n return { system, states };\n}\n\n/** The current state of every shard, throttled + coalesced as above. Never\n * throws: a failed query yields a clean, writable snapshot. */\nexport function shardStatus(shards: ShardRef[]): Promise<ShardStatus> {\n if (!inFlight) {\n inFlight = (async () => {\n try { return await query(shards); } finally { inFlight = undefined; }\n })();\n }\n return inFlight;\n}\n"]}
|
package/dist/vc-status.d.cts
CHANGED
|
@@ -5,6 +5,17 @@ import { VCStatusOptions, VCFileStatus } from '@wildwinter/simple-vc-lib';
|
|
|
5
5
|
interface ShardRef {
|
|
6
6
|
key: string;
|
|
7
7
|
path: string;
|
|
8
|
+
/**
|
|
9
|
+
* This path is the one that decides whether the KEY is untracked.
|
|
10
|
+
*
|
|
11
|
+
* A key usually spans several files, and "new" is the one state that does not
|
|
12
|
+
* fold by "any of them counts": a scene whose flow file is committed but whose
|
|
13
|
+
* sidecar has never been written is not a new scene, it is an edited one.
|
|
14
|
+
* Patterpad keys off the flow shard for exactly that reason. Mark the canonical
|
|
15
|
+
* path and the rest are ignored for `untracked`; mark none and the key never
|
|
16
|
+
* reports untracked.
|
|
17
|
+
*/
|
|
18
|
+
primary?: boolean;
|
|
8
19
|
}
|
|
9
20
|
/** One shard's version-control state. All-clean is `{ writable: true }`. */
|
|
10
21
|
interface ShardState {
|
|
@@ -16,6 +27,14 @@ interface ShardState {
|
|
|
16
27
|
lockedBy?: string[];
|
|
17
28
|
/** A newer revision exists on the server (get latest before editing). */
|
|
18
29
|
outOfDate?: boolean;
|
|
30
|
+
/** WE hold it: checked out / opened by the current user, still ours to edit.
|
|
31
|
+
* Distinct from `writable`, which says only that the bit is off. */
|
|
32
|
+
checkedOutByMe?: boolean;
|
|
33
|
+
/** Tracked, with local changes not yet committed. */
|
|
34
|
+
dirty?: boolean;
|
|
35
|
+
/** Not in version control yet. Decided by the `primary` path alone (see
|
|
36
|
+
* `ShardRef.primary`). */
|
|
37
|
+
untracked?: boolean;
|
|
19
38
|
}
|
|
20
39
|
interface ShardStatus {
|
|
21
40
|
/** The backend simple-vc-lib actually detected ("git" / "perforce" / ...). */
|
package/dist/vc-status.d.ts
CHANGED
|
@@ -5,6 +5,17 @@ import { VCStatusOptions, VCFileStatus } from '@wildwinter/simple-vc-lib';
|
|
|
5
5
|
interface ShardRef {
|
|
6
6
|
key: string;
|
|
7
7
|
path: string;
|
|
8
|
+
/**
|
|
9
|
+
* This path is the one that decides whether the KEY is untracked.
|
|
10
|
+
*
|
|
11
|
+
* A key usually spans several files, and "new" is the one state that does not
|
|
12
|
+
* fold by "any of them counts": a scene whose flow file is committed but whose
|
|
13
|
+
* sidecar has never been written is not a new scene, it is an edited one.
|
|
14
|
+
* Patterpad keys off the flow shard for exactly that reason. Mark the canonical
|
|
15
|
+
* path and the rest are ignored for `untracked`; mark none and the key never
|
|
16
|
+
* reports untracked.
|
|
17
|
+
*/
|
|
18
|
+
primary?: boolean;
|
|
8
19
|
}
|
|
9
20
|
/** One shard's version-control state. All-clean is `{ writable: true }`. */
|
|
10
21
|
interface ShardState {
|
|
@@ -16,6 +27,14 @@ interface ShardState {
|
|
|
16
27
|
lockedBy?: string[];
|
|
17
28
|
/** A newer revision exists on the server (get latest before editing). */
|
|
18
29
|
outOfDate?: boolean;
|
|
30
|
+
/** WE hold it: checked out / opened by the current user, still ours to edit.
|
|
31
|
+
* Distinct from `writable`, which says only that the bit is off. */
|
|
32
|
+
checkedOutByMe?: boolean;
|
|
33
|
+
/** Tracked, with local changes not yet committed. */
|
|
34
|
+
dirty?: boolean;
|
|
35
|
+
/** Not in version control yet. Decided by the `primary` path alone (see
|
|
36
|
+
* `ShardRef.primary`). */
|
|
37
|
+
untracked?: boolean;
|
|
19
38
|
}
|
|
20
39
|
interface ShardStatus {
|
|
21
40
|
/** The backend simple-vc-lib actually detected ("git" / "perforce" / ...). */
|
package/dist/vc-status.js
CHANGED
|
@@ -21,6 +21,7 @@ async function query(shards) {
|
|
|
21
21
|
const states = /* @__PURE__ */ new Map();
|
|
22
22
|
for (const s of shards) states.set(s.key, { writable: true });
|
|
23
23
|
const keyOf = new Map(shards.map((s) => [s.path, s.key]));
|
|
24
|
+
const isPrimary = new Set(shards.filter((s) => s.primary).map((s) => s.path));
|
|
24
25
|
const doRemote = Date.now() - lastRemoteAt >= REMOTE_STATUS_THROTTLE_MS;
|
|
25
26
|
let system = "filesystem";
|
|
26
27
|
try {
|
|
@@ -30,6 +31,9 @@ async function query(shards) {
|
|
|
30
31
|
system = st.system;
|
|
31
32
|
const acc = states.get(key);
|
|
32
33
|
if (!st.writable) acc.writable = false;
|
|
34
|
+
if (st.openedByMe) acc.checkedOutByMe = true;
|
|
35
|
+
if (st.dirty) acc.dirty = true;
|
|
36
|
+
if (st.tracked === false && isPrimary.has(st.filePath)) acc.untracked = true;
|
|
33
37
|
if (doRemote) {
|
|
34
38
|
if (st.lockedBy?.length) acc.lockedBy = [.../* @__PURE__ */ new Set([...acc.lockedBy ?? [], ...st.lockedBy])];
|
|
35
39
|
if (st.outOfDate) acc.outOfDate = true;
|
package/dist/vc-status.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vc-status.ts"],"names":[],"mappings":"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","file":"vc-status.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Per-shard version-control state, over @wildwinter/simple-vc-lib.\n//\n// The family's, because it follows from the SHAPE rather than from any app's\n// subject: these are all Electron apps editing JSON shards in a working copy,\n// so they all want to say \"locked by Bo\" before a save is refused rather than\n// after. Patterpad wrote it, Storyletter hand-ported it, and this is the third\n// copy becoming the only one.\n//\n// A SHARD here is just a key and a path: what the key means is the app's\n// business (a scene, a deck, a box's three files folded into one row).\n//\n// THROTTLING, which is the reason this is a module rather than a function call:\n// the local bits (`writable` - the on-disk read-only bit a lock VCS keys off)\n// are cheap and refresh on every call, so a save re-badges at once. The remote\n// bits (`lockedBy` / `outOfDate`) cost a SERVER round-trip under SVN and\n// Plastic, so they are re-queried at most once per window and otherwise reused\n// from the cache. The query is async (never blocks the main-process event loop)\n// and coalesced (a save, a focus and the poll timer arriving together make ONE\n// query), so a lock badge can never make typing stutter.\n//\n// Best-effort throughout: if the query throws (no tooling, broken repo) every\n// shard reports clean and writable, and the editor carries on.\n// ---------------------------------------------------------------------------\n\nimport { fileStatusAsync } from \"@wildwinter/simple-vc-lib\";\nimport type { VCFileStatus, VCStatusOptions } from \"@wildwinter/simple-vc-lib\";\n\n/** One shard to ask about: the key the renderer looks state up by, and its\n * absolute path on disk. */\nexport interface ShardRef {\n key: string;\n path: string;\n}\n\n/** One shard's version-control state. All-clean is `{ writable: true }`. */\nexport interface ShardState {\n /** Writable on disk right now. `false` = the read-only bit is set (a lock\n * VCS has not checked it out yet, or someone else holds it). */\n writable: boolean;\n /** Who else holds it open / locked (e.g. \"bo@bo-ws\") - we can read it, not\n * write it. */\n lockedBy?: string[];\n /** A newer revision exists on the server (get latest before editing). */\n outOfDate?: boolean;\n}\n\nexport interface ShardStatus {\n /** The backend simple-vc-lib actually detected (\"git\" / \"perforce\" / ...). */\n system: string;\n states: Map<string, ShardState>;\n}\n\n/** How long a remote (server round-trip) answer stays good for. */\nexport const REMOTE_STATUS_THROTTLE_MS = 15_000;\n\ntype StatusReader = (paths: string[], options?: VCStatusOptions) => Promise<VCFileStatus[]>;\n\nlet read: StatusReader = fileStatusAsync;\n/** What the app calls itself in the one warning this module can emit. */\nlet logPrefix = \"app-shell\";\n/** Name the app, for that warning. */\nexport function setVcLogPrefix(name: string): void { logPrefix = name; }\nlet lastRemoteAt = 0;\nlet cachedRemoteBits = new Map<string, { lockedBy?: string[]; outOfDate?: boolean }>();\nlet inFlight: Promise<ShardStatus> | undefined;\n\n/** Test seam, mirroring simple-vc-lib's own `setCommandRunner`: swap the status\n * reader for canned answers. Pass null to restore the real one. */\nexport function setStatusReader(reader: StatusReader | null): void {\n read = reader ?? fileStatusAsync;\n}\n\n/** Drop the throttle window and the cached remote bits - called when a project\n * opens, so a new project never inherits the last one's answers. */\nexport function resetShardStatus(): void {\n lastRemoteAt = 0;\n cachedRemoteBits = new Map();\n}\n\nasync function query(shards: ShardRef[]): Promise<ShardStatus> {\n const states = new Map<string, ShardState>();\n for (const s of shards) states.set(s.key, { writable: true });\n const keyOf = new Map(shards.map((s) => [s.path, s.key]));\n // Hit the server only when the window has elapsed; otherwise a cheap local read.\n const doRemote = Date.now() - lastRemoteAt >= REMOTE_STATUS_THROTTLE_MS;\n let system = \"filesystem\";\n try {\n for (const st of await read(shards.map((s) => s.path), { remote: doRemote })) {\n const key = keyOf.get(st.filePath);\n if (key === undefined) continue;\n system = st.system;\n const acc = states.get(key)!;\n if (!st.writable) acc.writable = false;\n if (doRemote) { // remote bits are authoritative only on a fresh server query\n if (st.lockedBy?.length) acc.lockedBy = [...new Set([...(acc.lockedBy ?? []), ...st.lockedBy])];\n if (st.outOfDate) acc.outOfDate = true;\n }\n }\n if (doRemote) { // snapshot the fresh remote bits for the throttled calls that follow\n lastRemoteAt = Date.now();\n cachedRemoteBits = new Map([...states].map(([key, s]) => [key, {\n ...(s.lockedBy !== undefined ? { lockedBy: s.lockedBy } : {}),\n ...(s.outOfDate !== undefined ? { outOfDate: s.outOfDate } : {}),\n }]));\n } else { // overlay the last known remote bits onto the fresh local snapshot\n for (const [key, acc] of states) {\n const cached = cachedRemoteBits.get(key);\n if (cached?.lockedBy?.length) acc.lockedBy = cached.lockedBy;\n if (cached?.outOfDate) acc.outOfDate = true;\n }\n }\n } catch (e) {\n // Tooling missing, repo error, timeout: everything reads clean + writable.\n console.warn(`${logPrefix}: the version-control status query failed - treating every shard as writable:`, e);\n }\n return { system, states };\n}\n\n/** The current state of every shard, throttled + coalesced as above. Never\n * throws: a failed query yields a clean, writable snapshot. */\nexport function shardStatus(shards: ShardRef[]): Promise<ShardStatus> {\n if (!inFlight) {\n inFlight = (async () => {\n try { return await query(shards); } finally { inFlight = undefined; }\n })();\n }\n return inFlight;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/vc-status.ts"],"names":[],"mappings":"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","file":"vc-status.js","sourcesContent":["// ---------------------------------------------------------------------------\n// Per-shard version-control state, over @wildwinter/simple-vc-lib.\n//\n// The family's, because it follows from the SHAPE rather than from any app's\n// subject: these are all Electron apps editing JSON shards in a working copy,\n// so they all want to say \"locked by Bo\" before a save is refused rather than\n// after. Patterpad wrote it, Storyletter hand-ported it, and this is the third\n// copy becoming the only one.\n//\n// A SHARD here is just a key and a path: what the key means is the app's\n// business (a scene, a deck, a box's three files folded into one row).\n//\n// THROTTLING, which is the reason this is a module rather than a function call:\n// the local bits (`writable` - the on-disk read-only bit a lock VCS keys off)\n// are cheap and refresh on every call, so a save re-badges at once. The remote\n// bits (`lockedBy` / `outOfDate`) cost a SERVER round-trip under SVN and\n// Plastic, so they are re-queried at most once per window and otherwise reused\n// from the cache. The query is async (never blocks the main-process event loop)\n// and coalesced (a save, a focus and the poll timer arriving together make ONE\n// query), so a lock badge can never make typing stutter.\n//\n// Best-effort throughout: if the query throws (no tooling, broken repo) every\n// shard reports clean and writable, and the editor carries on.\n// ---------------------------------------------------------------------------\n\nimport { fileStatusAsync } from \"@wildwinter/simple-vc-lib\";\nimport type { VCFileStatus, VCStatusOptions } from \"@wildwinter/simple-vc-lib\";\n\n/** One shard to ask about: the key the renderer looks state up by, and its\n * absolute path on disk. */\nexport interface ShardRef {\n key: string;\n path: string;\n /**\n * This path is the one that decides whether the KEY is untracked.\n *\n * A key usually spans several files, and \"new\" is the one state that does not\n * fold by \"any of them counts\": a scene whose flow file is committed but whose\n * sidecar has never been written is not a new scene, it is an edited one.\n * Patterpad keys off the flow shard for exactly that reason. Mark the canonical\n * path and the rest are ignored for `untracked`; mark none and the key never\n * reports untracked.\n */\n primary?: boolean;\n}\n\n/** One shard's version-control state. All-clean is `{ writable: true }`. */\nexport interface ShardState {\n /** Writable on disk right now. `false` = the read-only bit is set (a lock\n * VCS has not checked it out yet, or someone else holds it). */\n writable: boolean;\n /** Who else holds it open / locked (e.g. \"bo@bo-ws\") - we can read it, not\n * write it. */\n lockedBy?: string[];\n /** A newer revision exists on the server (get latest before editing). */\n outOfDate?: boolean;\n /** WE hold it: checked out / opened by the current user, still ours to edit.\n * Distinct from `writable`, which says only that the bit is off. */\n checkedOutByMe?: boolean;\n /** Tracked, with local changes not yet committed. */\n dirty?: boolean;\n /** Not in version control yet. Decided by the `primary` path alone (see\n * `ShardRef.primary`). */\n untracked?: boolean;\n}\n\nexport interface ShardStatus {\n /** The backend simple-vc-lib actually detected (\"git\" / \"perforce\" / ...). */\n system: string;\n states: Map<string, ShardState>;\n}\n\n/** How long a remote (server round-trip) answer stays good for. */\nexport const REMOTE_STATUS_THROTTLE_MS = 15_000;\n\ntype StatusReader = (paths: string[], options?: VCStatusOptions) => Promise<VCFileStatus[]>;\n\nlet read: StatusReader = fileStatusAsync;\n/** What the app calls itself in the one warning this module can emit. */\nlet logPrefix = \"app-shell\";\n/** Name the app, for that warning. */\nexport function setVcLogPrefix(name: string): void { logPrefix = name; }\nlet lastRemoteAt = 0;\nlet cachedRemoteBits = new Map<string, { lockedBy?: string[]; outOfDate?: boolean }>();\nlet inFlight: Promise<ShardStatus> | undefined;\n\n/** Test seam, mirroring simple-vc-lib's own `setCommandRunner`: swap the status\n * reader for canned answers. Pass null to restore the real one. */\nexport function setStatusReader(reader: StatusReader | null): void {\n read = reader ?? fileStatusAsync;\n}\n\n/** Drop the throttle window and the cached remote bits - called when a project\n * opens, so a new project never inherits the last one's answers. */\nexport function resetShardStatus(): void {\n lastRemoteAt = 0;\n cachedRemoteBits = new Map();\n}\n\nasync function query(shards: ShardRef[]): Promise<ShardStatus> {\n const states = new Map<string, ShardState>();\n for (const s of shards) states.set(s.key, { writable: true });\n const keyOf = new Map(shards.map((s) => [s.path, s.key]));\n const isPrimary = new Set(shards.filter((s) => s.primary).map((s) => s.path));\n // Hit the server only when the window has elapsed; otherwise a cheap local read.\n const doRemote = Date.now() - lastRemoteAt >= REMOTE_STATUS_THROTTLE_MS;\n let system = \"filesystem\";\n try {\n for (const st of await read(shards.map((s) => s.path), { remote: doRemote })) {\n const key = keyOf.get(st.filePath);\n if (key === undefined) continue;\n system = st.system;\n const acc = states.get(key)!;\n if (!st.writable) acc.writable = false;\n // Local bits, cheap on every provider, so never throttled.\n if (st.openedByMe) acc.checkedOutByMe = true;\n if (st.dirty) acc.dirty = true;\n if (st.tracked === false && isPrimary.has(st.filePath)) acc.untracked = true;\n if (doRemote) { // remote bits are authoritative only on a fresh server query\n if (st.lockedBy?.length) acc.lockedBy = [...new Set([...(acc.lockedBy ?? []), ...st.lockedBy])];\n if (st.outOfDate) acc.outOfDate = true;\n }\n }\n if (doRemote) { // snapshot the fresh remote bits for the throttled calls that follow\n lastRemoteAt = Date.now();\n cachedRemoteBits = new Map([...states].map(([key, s]) => [key, {\n ...(s.lockedBy !== undefined ? { lockedBy: s.lockedBy } : {}),\n ...(s.outOfDate !== undefined ? { outOfDate: s.outOfDate } : {}),\n }]));\n } else { // overlay the last known remote bits onto the fresh local snapshot\n for (const [key, acc] of states) {\n const cached = cachedRemoteBits.get(key);\n if (cached?.lockedBy?.length) acc.lockedBy = cached.lockedBy;\n if (cached?.outOfDate) acc.outOfDate = true;\n }\n }\n } catch (e) {\n // Tooling missing, repo error, timeout: everything reads clean + writable.\n console.warn(`${logPrefix}: the version-control status query failed - treating every shard as writable:`, e);\n }\n return { system, states };\n}\n\n/** The current state of every shard, throttled + coalesced as above. Never\n * throws: a failed query yields a clean, writable snapshot. */\nexport function shardStatus(shards: ShardRef[]): Promise<ShardStatus> {\n if (!inFlight) {\n inFlight = (async () => {\n try { return await query(shards); } finally { inFlight = undefined; }\n })();\n }\n return inFlight;\n}\n"]}
|
package/dist/vc.css
CHANGED
|
@@ -11,6 +11,16 @@
|
|
|
11
11
|
.vc-badge.vc-stale {
|
|
12
12
|
color: var(--warn);
|
|
13
13
|
}
|
|
14
|
+
.vc-badge.vc-mine {
|
|
15
|
+
color: var(--accent);
|
|
16
|
+
}
|
|
17
|
+
.vc-badge.vc-dirty {
|
|
18
|
+
color: var(--accent);
|
|
19
|
+
font-size: 0.6rem;
|
|
20
|
+
}
|
|
21
|
+
.vc-badge.vc-new {
|
|
22
|
+
color: var(--muted);
|
|
23
|
+
}
|
|
14
24
|
.vc-badge.vc-frozen {
|
|
15
25
|
color: var(--muted);
|
|
16
26
|
opacity: 0.6;
|
package/dist/vc.css.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vc.css"],"sourcesContent":["/* Version-control surfacing (vc-view.ts): the badge a row flies, and what a\n locked document looks like. Monochrome and themed - never colour emoji - so\n each glyph inherits its palette and reads the same in every theme. */\n.vc-badge { margin-left: 6px; font-size: 0.78rem; line-height: 1; opacity: 0.85; }\n.vc-badge.vc-locked { color: var(--danger); }\n.vc-badge.vc-stale { color: var(--warn); }\n.vc-badge.vc-frozen { color: var(--muted); opacity: 0.6; }\n\n/* A document somebody else holds: everything reads, nothing types. */\n.vc-readonly { position: relative; }\n.vc-off { opacity: 0.6; cursor: not-allowed; }\n.vc-lock {\n display: flex; align-items: center; gap: 8px;\n margin-bottom: 10px; padding: 7px 10px;\n border: 1px solid var(--line); border-left: 3px solid var(--danger);\n border-radius: var(--radius-sm);\n background: var(--card);\n font: 0.8rem/1.4 var(--font-ui); color: var(--ink);\n}\n.vc-lock-glyph { color: var(--danger); }\n"],"mappings":";AAGA,CAAC;AAAW,eAAa;AAAK,aAAW;AAAS,eAAa;AAAG,WAAS;AAAM;AACjF,CADC,QACQ,CAAC;AAAY,SAAO,IAAI;AAAW;AAC5C,CAFC,QAEQ,CAAC;AAAW,SAAO,IAAI;AAAS;
|
|
1
|
+
{"version":3,"sources":["../src/vc.css"],"sourcesContent":["/* Version-control surfacing (vc-view.ts): the badge a row flies, and what a\n locked document looks like. Monochrome and themed - never colour emoji - so\n each glyph inherits its palette and reads the same in every theme. */\n.vc-badge { margin-left: 6px; font-size: 0.78rem; line-height: 1; opacity: 0.85; }\n.vc-badge.vc-locked { color: var(--danger); }\n.vc-badge.vc-stale { color: var(--warn); }\n/* The three \"this one is yours\" states. Accent rather than danger or warn:\n nothing is wrong with a file you are holding, so it must not read as a\n problem sitting in a list beside ones that are. */\n.vc-badge.vc-mine { color: var(--accent); }\n.vc-badge.vc-dirty { color: var(--accent); font-size: 0.6rem; }\n.vc-badge.vc-new { color: var(--muted); }\n.vc-badge.vc-frozen { color: var(--muted); opacity: 0.6; }\n\n/* A document somebody else holds: everything reads, nothing types. */\n.vc-readonly { position: relative; }\n.vc-off { opacity: 0.6; cursor: not-allowed; }\n.vc-lock {\n display: flex; align-items: center; gap: 8px;\n margin-bottom: 10px; padding: 7px 10px;\n border: 1px solid var(--line); border-left: 3px solid var(--danger);\n border-radius: var(--radius-sm);\n background: var(--card);\n font: 0.8rem/1.4 var(--font-ui); color: var(--ink);\n}\n.vc-lock-glyph { color: var(--danger); }\n"],"mappings":";AAGA,CAAC;AAAW,eAAa;AAAK,aAAW;AAAS,eAAa;AAAG,WAAS;AAAM;AACjF,CADC,QACQ,CAAC;AAAY,SAAO,IAAI;AAAW;AAC5C,CAFC,QAEQ,CAAC;AAAW,SAAO,IAAI;AAAS;AAIzC,CANC,QAMQ,CAAC;AAAU,SAAO,IAAI;AAAW;AAC1C,CAPC,QAOQ,CAAC;AAAW,SAAO,IAAI;AAAW,aAAW;AAAQ;AAC9D,CARC,QAQQ,CAAC;AAAS,SAAO,IAAI;AAAU;AACxC,CATC,QASQ,CAAC;AAAY,SAAO,IAAI;AAAU,WAAS;AAAK;AAGzD,CAAC;AAAc,YAAU;AAAU;AACnC,CAAC;AAAS,WAAS;AAAK,UAAQ;AAAa;AAC7C,CAAC;AACC,WAAS;AAAM,eAAa;AAAQ,OAAK;AACzC,iBAAe;AAAM,WAAS,IAAI;AAClC,UAAQ,IAAI,MAAM,IAAI;AAAS,eAAa,IAAI,MAAM,IAAI;AAC1D,iBAAe,IAAI;AACnB,cAAY,IAAI;AAChB,QAAM,MAAM,CAAC,IAAI,IAAI;AAAY,SAAO,IAAI;AAC9C;AACA,CAAC;AAAgB,SAAO,IAAI;AAAW;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wildwinter/app-shell",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "The shared shell for the editor family (Patterpad, Storylet Studio): the app-agnostic design-token grammar, the per-entity colour hash, and the vanilla-TS DOM primitive. Two palettes, one grammar - each app supplies its own palette/fonts over these tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|