@lapis-notes/workspace 0.3.2 → 0.3.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,62 @@
1
1
  # @lapis-notes/workspace
2
2
 
3
+ ## 0.3.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#85](https://github.com/lapismd/lapis-notes/pull/85)
8
+ [`8325b5e`](https://github.com/lapismd/lapis-notes/commit/8325b5eb7adef358ac577b457278a19637c13571)
9
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Reuse scanned identities
10
+ during unchanged startup, exclude installed plugin assets from content
11
+ polling, avoid eagerly reading lazy ESM chunks, queue plugin operations with
12
+ independent progress and cancellation, and enable bundled AI by default on the
13
+ Rust server.
14
+
15
+ Renew the native AI feature session after disable/re-enable so startup
16
+ recovery cannot reuse a fenced session.
17
+
18
+ - [#82](https://github.com/lapismd/lapis-notes/pull/82)
19
+ [`e7b96fb`](https://github.com/lapismd/lapis-notes/commit/e7b96fb7f901c6ffff7e92815b29a82bea5c5264)
20
+ Thanks
21
+ [@lapismd-dependency-bot](https://github.com/apps/lapismd-dependency-bot)! -
22
+ Refresh first-party Lapis dependencies to the published versions recorded in
23
+ this pull request.
24
+ - Updated dependencies
25
+ [[`8325b5e`](https://github.com/lapismd/lapis-notes/commit/8325b5eb7adef358ac577b457278a19637c13571),
26
+ [`e7b96fb`](https://github.com/lapismd/lapis-notes/commit/e7b96fb7f901c6ffff7e92815b29a82bea5c5264)]:
27
+ - @lapis-notes/api@0.2.7
28
+ - @lapis-notes/ui@0.1.8
29
+
30
+ ## 0.3.4
31
+
32
+ ### Patch Changes
33
+
34
+ - [#70](https://github.com/lapismd/lapis-notes/pull/70)
35
+ [`df60318`](https://github.com/lapismd/lapis-notes/commit/df603187df2680d46720ee2af0a226c6fbb25a22)
36
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Show representative
37
+ skeletons while the plugin registry catalog and details load, preserving
38
+ existing empty/error states, verified metadata, and App-owned actions.
39
+ - Updated dependencies
40
+ [[`5bd58d8`](https://github.com/lapismd/lapis-notes/commit/5bd58d8033062923f09549fa341d84f22b143452),
41
+ [`8a7ec3c`](https://github.com/lapismd/lapis-notes/commit/8a7ec3c3d1d1610e8be0e3b29aab4b2f4188a0c5),
42
+ [`5292b52`](https://github.com/lapismd/lapis-notes/commit/5292b52ebc37318fcfad7971e4e309cf3afaff18)]:
43
+ - @lapis-notes/api@0.2.6
44
+
45
+ ## 0.3.3
46
+
47
+ ### Patch Changes
48
+
49
+ - [#66](https://github.com/lapismd/lapis-notes/pull/66)
50
+ [`6086980`](https://github.com/lapismd/lapis-notes/commit/6086980be8a0d9cf6e697a689b0c17f409afdc3f)
51
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Consume published Design
52
+ Core 0.10.1 so browser hosts can reserve leading and trailing native controls
53
+ and title-bar height, with a continuous divider when sidebars collapse. Keep
54
+ portable registry ranges and existing compatibility.
55
+ - Updated dependencies
56
+ [[`6086980`](https://github.com/lapismd/lapis-notes/commit/6086980be8a0d9cf6e697a689b0c17f409afdc3f)]:
57
+ - @lapis-notes/api@0.2.5
58
+ - @lapis-notes/ui@0.1.7
59
+
3
60
  ## 0.3.2
4
61
 
5
62
  ### Patch Changes
@@ -26,6 +26,7 @@
26
26
  import PluginBrowserLanding from "./PluginBrowserLanding.svelte";
27
27
  import PluginBrowserDirectory from "./PluginBrowserDirectory.svelte";
28
28
  import PluginRegistryContentState from "./PluginRegistryContentState.svelte";
29
+ import PluginRegistrySkeleton from "./PluginRegistrySkeleton.svelte";
29
30
  import PluginBrowserDetail from "./PluginBrowserDetail.svelte";
30
31
  import "./community-presentation/community-typography.css";
31
32
  export interface PluginBrowserNavigation {
@@ -130,10 +131,7 @@
130
131
  registry.actionFeedback?.pluginId === selected.id
131
132
  ? registry.actionFeedback
132
133
  : null;
133
- if (
134
- registry.runningAction === `install:${selected.id}` ||
135
- registry.runningAction === `update:${selected.id}`
136
- )
134
+ if (registry.isPluginActionPending(selected.id))
137
135
  return { state: "installing" };
138
136
  if (feedback?.state === "error")
139
137
  return { state: "rejected", reason: feedback.message };
@@ -409,10 +407,9 @@
409
407
  label="Registry content"
410
408
  >
411
409
  <div class="registry-browser">
412
- {#if registry.initialLoading}<PluginRegistryContentState
413
- kind="loading"
414
- heading="Loading plugins"
415
- description="Reading your verified registry…"
410
+ {#if registry.initialLoading}<PluginRegistrySkeleton
411
+ layout={navigation.route.page === "plugin" ? "detail" : navigation.route.page}
412
+ label="Loading plugins"
416
413
  />
417
414
  {:else if registry.lastError && !registry.catalogEntries.length}<PluginRegistryContentState
418
415
  kind="warning"
@@ -495,10 +492,9 @@
495
492
  persist();
496
493
  }}
497
494
  />
498
- {:else if selected && session.loading}<PluginRegistryContentState
499
- kind="loading"
500
- heading="Loading plugin details"
501
- description="Reading verified plugin metadata…"
495
+ {:else if selected && session.loading}<PluginRegistrySkeleton
496
+ layout="detail"
497
+ label="Loading plugin details"
502
498
  />
503
499
  {:else}<PluginRegistryContentState
504
500
  kind="empty"
@@ -5,6 +5,7 @@
5
5
  import SearchX from "@lucide/svelte/icons/search-x";
6
6
  import Sparkles from "@lucide/svelte/icons/sparkles";
7
7
  import * as Button from "@lapismd/design-core/shadcn/button";
8
+ import PluginRegistrySkeleton from "./PluginRegistrySkeleton.svelte";
8
9
 
9
10
  let {
10
11
  kind,
@@ -29,6 +30,9 @@
29
30
  } = $props();
30
31
  </script>
31
32
 
33
+ {#if kind === "loading"}
34
+ <PluginRegistrySkeleton label={heading} />
35
+ {:else}
32
36
  <div
33
37
  class="lapis-plugin-registry-state"
34
38
  data-ui-component="plugin-registry-content-state"
@@ -56,7 +60,7 @@
56
60
  <AlertTriangle />
57
61
  {:else}
58
62
  <PackageIcon />
59
- {#if kind !== "loading"}<Sparkles />{/if}
63
+ <Sparkles />
60
64
  {/if}
61
65
  </div>
62
66
  <div class="lapis-plugin-registry-state__body">
@@ -78,3 +82,4 @@
78
82
  {/if}
79
83
  </div>
80
84
  </div>
85
+ {/if}
@@ -771,8 +771,9 @@
771
771
  size="icon"
772
772
  variant="outline"
773
773
  aria-label={`Update ${installedName(record)}`}
774
- disabled={registry.runningAction ===
775
- `update:${record.pluginId}`}
774
+ disabled={registry.isPluginActionPending(
775
+ record.pluginId,
776
+ )}
776
777
  onclick={() => void updatePlugin(update)}
777
778
  ><ArrowUpCircle /></Button.Root
778
779
  >
@@ -922,8 +923,7 @@
922
923
  ><Check data-icon="inline-start" />Bundled</Button.Root
923
924
  >
924
925
  {:else}
925
- {@const installing =
926
- registry.runningAction === `install:${entry.id}`}
926
+ {@const installing = registry.isPluginActionPending(entry.id)}
927
927
  <Button.Root
928
928
  size="sm"
929
929
  aria-label={installing
@@ -1041,7 +1041,7 @@
1041
1041
  variant="outline"
1042
1042
  aria-label={`Update ${update.name}`}
1043
1043
  disabled={!update.canUpdate ||
1044
- registry.runningAction === `update:${update.id}`}
1044
+ registry.isPluginActionPending(update.id)}
1045
1045
  onclick={() => void updatePlugin(update)}
1046
1046
  ><ArrowUpCircle /></Button.Root
1047
1047
  >
@@ -0,0 +1,112 @@
1
+ <script lang="ts">
2
+ import { Skeleton } from "@lapismd/design-core/shadcn/skeleton";
3
+
4
+ let {
5
+ layout = "list",
6
+ label = "Loading plugins",
7
+ }: {
8
+ layout?: "landing" | "directory" | "detail" | "list";
9
+ label?: string;
10
+ } = $props();
11
+ </script>
12
+
13
+ <div
14
+ class="registry-skeleton"
15
+ data-ui-component="plugin-registry-skeleton"
16
+ data-layout={layout}
17
+ aria-busy="true"
18
+ >
19
+ <span class="sr-only" role="status">{label}</span>
20
+ <div class="registry-skeleton__layout" aria-hidden="true">
21
+ <header class="registry-skeleton__header">
22
+ {#if layout === "detail"}
23
+ <Skeleton class="registry-skeleton__avatar" />
24
+ {/if}
25
+ <div class="registry-skeleton__copy">
26
+ <Skeleton class="registry-skeleton__title" />
27
+ <Skeleton class="registry-skeleton__line" />
28
+ <Skeleton class="registry-skeleton__short" />
29
+ </div>
30
+ {#if layout === "detail" || layout === "directory"}
31
+ <Skeleton class="registry-skeleton__action" />
32
+ {/if}
33
+ </header>
34
+ {#if layout === "detail"}
35
+ <Skeleton class="registry-skeleton__banner" data-ui-part="banner" />
36
+ <div class="registry-skeleton__detail">
37
+ <div class="registry-skeleton__copy" data-ui-part="overview">
38
+ <Skeleton class="registry-skeleton__title" />
39
+ {#each Array(5) as _}<Skeleton class="registry-skeleton__line" />{/each}
40
+ </div>
41
+ <aside class="registry-skeleton__copy" data-ui-part="metadata">
42
+ <Skeleton class="registry-skeleton__short" />
43
+ {#each Array(5) as _}<Skeleton class="registry-skeleton__line" />{/each}
44
+ </aside>
45
+ </div>
46
+ {:else}
47
+ <div class="registry-skeleton__cards" data-ui-part="cards">
48
+ {#each Array(layout === "landing" ? 3 : layout === "directory" ? 6 : 4) as _}
49
+ <div class="registry-skeleton__card">
50
+ <Skeleton class="registry-skeleton__avatar" />
51
+ <div class="registry-skeleton__copy">
52
+ <Skeleton class="registry-skeleton__short" />
53
+ <Skeleton class="registry-skeleton__line" />
54
+ <Skeleton class="registry-skeleton__line" />
55
+ </div>
56
+ </div>
57
+ {/each}
58
+ </div>
59
+ {/if}
60
+ </div>
61
+ </div>
62
+
63
+ <style>
64
+ .registry-skeleton {
65
+ container-type: inline-size;
66
+ width: 100%;
67
+ min-width: 0;
68
+ padding: 1.5rem;
69
+ color: var(--ui-workspace-foreground, var(--foreground));
70
+ --ui-skeleton-background: color-mix(in srgb, var(--ui-workspace-foreground, var(--foreground)) 9%, var(--ui-workspace-view-background, var(--background)));
71
+ }
72
+ .registry-skeleton__layout,
73
+ .registry-skeleton__copy,
74
+ .registry-skeleton__cards,
75
+ .registry-skeleton__detail {
76
+ display: grid;
77
+ min-width: 0;
78
+ gap: 1rem;
79
+ }
80
+ .registry-skeleton__layout { gap: 2rem; }
81
+ .registry-skeleton__header,
82
+ .registry-skeleton__card {
83
+ display: flex;
84
+ min-width: 0;
85
+ align-items: center;
86
+ gap: 1rem;
87
+ }
88
+ .registry-skeleton__card {
89
+ border: 1px solid var(--ui-workspace-border, var(--border));
90
+ border-radius: var(--radius);
91
+ padding: 1rem;
92
+ }
93
+ .registry-skeleton__copy { flex: 1; }
94
+ .registry-skeleton :global(.registry-skeleton__avatar) {
95
+ width: 3.5rem;
96
+ height: 3.5rem;
97
+ flex-shrink: 0;
98
+ }
99
+ .registry-skeleton :global(.registry-skeleton__title) { width: min(75%, 20rem); height: 2rem; }
100
+ .registry-skeleton :global(.registry-skeleton__line) { height: 0.875rem; width: 100%; }
101
+ .registry-skeleton :global(.registry-skeleton__short) { height: 0.875rem; width: 60%; }
102
+ .registry-skeleton :global(.registry-skeleton__action) { height: 2.25rem; width: 6rem; }
103
+ .registry-skeleton :global(.registry-skeleton__banner) { width: 100%; aspect-ratio: 3 / 1; }
104
+ @container (min-width: 44rem) {
105
+ [data-layout="landing"] .registry-skeleton__cards,
106
+ [data-layout="directory"] .registry-skeleton__cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
107
+ .registry-skeleton__detail { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 2rem; }
108
+ }
109
+ @media (prefers-reduced-motion: reduce) {
110
+ .registry-skeleton :global([data-ui-component="skeleton"]) { animation: none; }
111
+ }
112
+ </style>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ layout?: "landing" | "directory" | "detail" | "list";
3
+ label?: string;
4
+ };
5
+ declare const PluginRegistrySkeleton: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type PluginRegistrySkeleton = ReturnType<typeof PluginRegistrySkeleton>;
7
+ export default PluginRegistrySkeleton;
@@ -14,6 +14,8 @@ export declare class PluginRegistryController {
14
14
  initialLoading: boolean;
15
15
  refreshing: boolean;
16
16
  runningAction: string | null;
17
+ pendingPluginActions: Record<string, true>;
18
+ isPluginActionPending(pluginId: string): boolean;
17
19
  actionFeedback: PluginRegistryActionFeedback | null;
18
20
  pluginLifecycleRevision: number;
19
21
  private references;
@@ -25,6 +25,10 @@ export class PluginRegistryController {
25
25
  initialLoading = $state(true);
26
26
  refreshing = $state(false);
27
27
  runningAction = $state(null);
28
+ pendingPluginActions = $state({});
29
+ isPluginActionPending(pluginId) {
30
+ return this.pendingPluginActions[pluginId] === true;
31
+ }
28
32
  actionFeedback = $state(null);
29
33
  pluginLifecycleRevision = $state(0);
30
34
  references = 0;
@@ -110,20 +114,32 @@ export class PluginRegistryController {
110
114
  this.refreshTask = null;
111
115
  }
112
116
  async runPluginAction(actionId, pluginId, title, action) {
113
- if (this.runningAction)
117
+ if (this.isPluginActionPending(pluginId))
114
118
  return;
115
- this.runningAction = actionId;
116
- this.actionFeedback = { pluginId, state: "running", message: title };
119
+ this.pendingPluginActions = {
120
+ ...this.pendingPluginActions,
121
+ [pluginId]: true,
122
+ };
117
123
  try {
118
- await withPluginInstallProgress(this.app, { pluginId, title }, action);
119
- await this.refresh(false);
120
- this.actionFeedback = {
121
- pluginId,
122
- state: "success",
123
- message: actionId.startsWith("update:")
124
- ? "Update installed"
125
- : "Installed",
126
- };
124
+ await withPluginInstallProgress(this.app, { pluginId, title }, async (signal) => {
125
+ this.runningAction = actionId;
126
+ this.actionFeedback = { pluginId, state: "running", message: title };
127
+ try {
128
+ await action(signal);
129
+ await this.refresh(false);
130
+ this.actionFeedback = {
131
+ pluginId,
132
+ state: "success",
133
+ message: actionId.startsWith("update:")
134
+ ? "Update installed"
135
+ : "Installed",
136
+ };
137
+ }
138
+ finally {
139
+ if (this.runningAction === actionId)
140
+ this.runningAction = null;
141
+ }
142
+ });
127
143
  }
128
144
  catch (error) {
129
145
  if (isAbortError(error)) {
@@ -139,7 +155,8 @@ export class PluginRegistryController {
139
155
  new Notice(`Plugin registry action failed: ${registryErrorMessage(error)}`, undefined, this.app);
140
156
  }
141
157
  finally {
142
- this.runningAction = null;
158
+ const { [pluginId]: _finished, ...pending } = this.pendingPluginActions;
159
+ this.pendingPluginActions = pending;
143
160
  }
144
161
  }
145
162
  install(entry, version) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lapis-notes/workspace",
3
- "version": "0.3.2",
3
+ "version": "0.3.5",
4
4
  "description": "Lapis Notes workspace shell backed by design-core",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -38,10 +38,10 @@
38
38
  }
39
39
  },
40
40
  "dependencies": {
41
- "@lapis-notes/api": "^0.2.4",
42
- "@lapismd/design-core": "^0.8.0 || ^0.9.0",
43
- "@lucide/svelte": "^0.523.0",
44
- "@lapis-notes/ui": "^0.1.6"
41
+ "@lapis-notes/api": "^0.2.7",
42
+ "@lapis-notes/ui": "^0.1.8",
43
+ "@lapismd/design-core": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.12.0",
44
+ "@lucide/svelte": "^0.523.0"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "svelte": "^5.0.0"