@abide/abide 0.43.1 → 0.44.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/AGENTS.md +293 -219
- package/CHANGELOG.md +39 -0
- package/README.md +191 -67
- package/package.json +2 -2
- package/src/abideLsp.ts +8 -4
- package/src/abideResolverPlugin.ts +11 -1
- package/src/build.ts +9 -0
- package/src/buildDisconnected.ts +2 -0
- package/src/devEntry.ts +1 -0
- package/src/lib/bundle/probeAbideServer.ts +1 -1
- package/src/lib/cli/parseArgvForRpc.ts +4 -2
- package/src/lib/cli/types/CliManifest.ts +6 -4
- package/src/lib/mcp/annotationsForMethod.ts +1 -1
- package/src/lib/mcp/toolResultFromResponse.ts +1 -1
- package/src/lib/mcp/types/JsonRpcRequest.ts +3 -3
- package/src/lib/server/AppModule.ts +1 -1
- package/src/lib/server/cli/handleCliDownload.ts +7 -5
- package/src/lib/server/cli/installScript.ts +3 -3
- package/src/lib/server/prompts/promptRegistry.ts +1 -1
- package/src/lib/server/prompts/types/Prompt.ts +2 -1
- package/src/lib/server/rpc/dispatchRpcInProcess.ts +2 -2
- package/src/lib/server/rpc/types/RpcHelper.ts +1 -1
- package/src/lib/server/runtime/createPublicAssetServer.ts +2 -2
- package/src/lib/server/runtime/createRouteDispatcher.ts +1 -1
- package/src/lib/server/runtime/createServer.ts +21 -0
- package/src/lib/server/runtime/createUiPageRenderer.ts +7 -11
- package/src/lib/server/runtime/crossOriginForbidden.ts +2 -2
- package/src/lib/server/runtime/crossOriginGate.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +2 -2
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +1 -1
- package/src/lib/server/runtime/logExposedSurfaces.ts +3 -4
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +3 -8
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +10 -7
- package/src/lib/server/runtime/streamFromIterator.ts +2 -2
- package/src/lib/server/runtime/types/InspectorRpc.ts +2 -2
- package/src/lib/server/socket.ts +1 -1
- package/src/lib/server/sockets/createSocketDispatcher.ts +2 -2
- package/src/lib/server/sockets/socketRegistry.ts +2 -2
- package/src/lib/server/sockets/types/SocketClientFrame.ts +3 -3
- package/src/lib/shared/CACHE_WRAPPED.ts +3 -2
- package/src/lib/shared/REMOTE_FUNCTION.ts +1 -1
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +3 -2
- package/src/lib/shared/bodyValueForKind.ts +35 -0
- package/src/lib/shared/cache.ts +1 -1
- package/src/lib/shared/cacheEntryFromSnapshot.ts +23 -18
- package/src/lib/shared/createPushIterator.ts +1 -1
- package/src/lib/shared/decodeResponse.ts +11 -8
- package/src/lib/shared/hasReplayableRequest.ts +17 -0
- package/src/lib/shared/html.ts +7 -26
- package/src/lib/shared/manifestModule.ts +2 -2
- package/src/lib/shared/online.ts +1 -1
- package/src/lib/shared/outboxProbeSlot.ts +1 -1
- package/src/lib/shared/pageUrlForFile.ts +1 -1
- package/src/lib/shared/routeParamsShape.ts +20 -0
- package/src/lib/shared/snapshotShippable.ts +22 -0
- package/src/lib/shared/types/CacheEntry.ts +1 -1
- package/src/lib/shared/types/CacheInvalidation.ts +3 -3
- package/src/lib/shared/types/ClientFlags.ts +5 -3
- package/src/lib/shared/types/LastConnection.ts +1 -1
- package/src/lib/shared/url.ts +4 -1
- package/src/lib/shared/withBase.ts +1 -2
- package/src/lib/shared/writeDts.ts +2 -2
- package/src/lib/shared/writeRoutesDts.ts +3 -31
- package/src/lib/test/createTestSocketChannel.ts +3 -3
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +1 -1
- package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +40 -6
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +1 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +49 -37
- package/src/lib/ui/compile/abideUiPlugin.ts +1 -1
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +3 -1
- package/src/lib/ui/compile/awaitPlan.ts +14 -2
- package/src/lib/ui/compile/catchBinding.ts +8 -0
- package/src/lib/ui/compile/classStyleMergePlan.ts +108 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +11 -0
- package/src/lib/ui/compile/compileComponent.ts +4 -4
- package/src/lib/ui/compile/compileModule.ts +20 -6
- package/src/lib/ui/compile/compileSSR.ts +12 -10
- package/src/lib/ui/compile/compileShadow.ts +88 -11
- package/src/lib/ui/compile/composeProps.ts +3 -3
- package/src/lib/ui/compile/createShadowLanguageService.ts +30 -2
- package/src/lib/ui/compile/createShadowProgram.ts +2 -1
- package/src/lib/ui/compile/createShadowScope.ts +42 -0
- package/src/lib/ui/compile/decodeHtmlEntities.ts +4 -0
- package/src/lib/ui/compile/desugarSignals.ts +12 -7
- package/src/lib/ui/compile/eachPlan.ts +61 -0
- package/src/lib/ui/compile/elementPlan.ts +55 -0
- package/src/lib/ui/compile/encodeSemanticTokens.ts +24 -1
- package/src/lib/ui/compile/generateBuild.ts +150 -127
- package/src/lib/ui/compile/generateSSR.ts +112 -73
- package/src/lib/ui/compile/ifPlan.ts +7 -1
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +25 -0
- package/src/lib/ui/compile/isPlainIdentifier.ts +7 -0
- package/src/lib/ui/compile/isTextLeaf.ts +8 -7
- package/src/lib/ui/compile/lowerContext.ts +36 -46
- package/src/lib/ui/compile/lowerDocAccess.ts +2 -2
- package/src/lib/ui/compile/lowerScript.ts +1 -1
- package/src/lib/ui/compile/markupTokens.ts +313 -0
- package/src/lib/ui/compile/pagePropsType.ts +23 -0
- package/src/lib/ui/compile/parseTemplate.ts +131 -61
- package/src/lib/ui/compile/reactiveBinding.ts +47 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +44 -10
- package/src/lib/ui/compile/skeletonable.ts +5 -5
- package/src/lib/ui/compile/snippetPlan.ts +28 -0
- package/src/lib/ui/compile/spreadExcludedNames.ts +4 -4
- package/src/lib/ui/compile/structuralBlockTokens.ts +3 -4
- package/src/lib/ui/compile/switchPlan.ts +7 -1
- package/src/lib/ui/compile/tryPlan.ts +8 -1
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +2 -2
- package/src/lib/ui/compile/types/Binding.ts +24 -0
- package/src/lib/ui/compile/types/CompiledShadow.ts +13 -1
- package/src/lib/ui/compile/types/ElementPlan.ts +43 -0
- package/src/lib/ui/compile/types/ShadowKind.ts +9 -0
- package/src/lib/ui/compile/types/ShadowScope.ts +19 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +7 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +8 -8
- package/src/lib/ui/compile/withBindings.ts +45 -0
- package/src/lib/ui/createScope.ts +2 -2
- package/src/lib/ui/dom/MATHML_NAMESPACE.ts +2 -2
- package/src/lib/ui/dom/SVG_NAMESPACE.ts +3 -2
- package/src/lib/ui/dom/appendText.ts +1 -1
- package/src/lib/ui/dom/applyResolved.ts +1 -1
- package/src/lib/ui/dom/buildDetachedRange.ts +4 -3
- package/src/lib/ui/dom/cloneStatic.ts +3 -2
- package/src/lib/ui/dom/each.ts +26 -7
- package/src/lib/ui/dom/foreignWrapperTag.ts +2 -2
- package/src/lib/ui/dom/mountChild.ts +8 -1
- package/src/lib/ui/dom/readCall.ts +6 -4
- package/src/lib/ui/dom/scopeLabel.ts +3 -3
- package/src/lib/ui/dom/switchBlock.ts +3 -1
- package/src/lib/ui/dom/types/EachRow.ts +4 -0
- package/src/lib/ui/dom/types/SkeletonHoles.ts +4 -2
- package/src/lib/ui/history.ts +1 -2
- package/src/lib/ui/html.ts +28 -0
- package/src/lib/ui/installHotBridge.ts +0 -2
- package/src/lib/ui/renderChain.ts +16 -3
- package/src/lib/ui/router.ts +24 -3
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +1 -1
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +8 -0
- package/src/lib/ui/runtime/OUTLET_MARKER.ts +2 -2
- package/src/lib/ui/runtime/RANGE_MARKER.ts +1 -1
- package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +9 -3
- package/src/lib/ui/runtime/captureModelDoc.ts +1 -1
- package/src/lib/ui/runtime/createDoc.ts +5 -1
- package/src/lib/ui/runtime/createEffectNode.ts +5 -5
- package/src/lib/ui/runtime/flushEffects.ts +22 -9
- package/src/lib/ui/runtime/historyEntries.ts +1 -1
- package/src/lib/ui/runtime/track.ts +4 -2
- package/src/lib/ui/runtime/trigger.ts +34 -20
- package/src/lib/ui/runtime/types/Route.ts +2 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +3 -2
- package/src/lib/ui/runtime/types/UiProps.ts +9 -12
- package/src/lib/ui/runtime/walkPath.ts +15 -3
- package/src/lib/ui/startClient.ts +41 -14
- package/src/lib/ui/state.ts +3 -3
- package/src/lib/ui/types/Scope.ts +3 -3
- package/src/serverBuildPlugins.ts +11 -5
- package/src/zodCjsPlugin.ts +36 -0
- package/template/.zed/settings.json +7 -1
- package/template/package.json +3 -0
- package/template/src/ui/pages/layout.abide +3 -5
package/src/lib/ui/router.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { outlet } from './dom/outlet.ts'
|
|
|
4
4
|
import { effect } from './effect.ts'
|
|
5
5
|
import { matchRoute } from './matchRoute.ts'
|
|
6
6
|
import { navigatePath } from './navigate.ts'
|
|
7
|
+
import { CHILD_PRESENT } from './runtime/CHILD_PRESENT.ts'
|
|
7
8
|
import { clientPage } from './runtime/clientPage.ts'
|
|
8
9
|
import { enterRenderPass } from './runtime/enterRenderPass.ts'
|
|
9
10
|
import { exitRenderPass } from './runtime/exitRenderPass.ts'
|
|
@@ -15,6 +16,7 @@ import type { AbideHistoryState } from './runtime/types/AbideHistoryState.ts'
|
|
|
15
16
|
import type { NavVerdict } from './runtime/types/NavVerdict.ts'
|
|
16
17
|
import type { Route } from './runtime/types/Route.ts'
|
|
17
18
|
import type { RouteLoader } from './runtime/types/RouteLoader.ts'
|
|
19
|
+
import type { UiProps } from './runtime/types/UiProps.ts'
|
|
18
20
|
import { untrack } from './runtime/untrack.ts'
|
|
19
21
|
|
|
20
22
|
/* An outlet boundary — the `<!--abide:outlet-->`…`<!--/abide:outlet-->` marker pair a
|
|
@@ -192,6 +194,23 @@ export function router(
|
|
|
192
194
|
outlet(host)
|
|
193
195
|
rootBoundary = PENDING_OUTLET.current as Boundary
|
|
194
196
|
}
|
|
197
|
+
/* Route params as reactive thunks: reading `clientPage.value.params` inside the
|
|
198
|
+
thunk tracks the page signal, so an in-place same-route hop (params change, the
|
|
199
|
+
page stays mounted) re-runs each `props()` derive. The key set is stable across
|
|
200
|
+
same-route hops, so the bag need not rebuild. A layout also gets `$children`
|
|
201
|
+
set to `CHILD_PRESENT` when a child layer exists below it, read by
|
|
202
|
+
`{#if children}` (a layout's `{children()}` lowers to its `outlet()` boundary,
|
|
203
|
+
so it ignores this value). */
|
|
204
|
+
const propsBag = (hasChild: boolean): UiProps => {
|
|
205
|
+
const bag: UiProps = {}
|
|
206
|
+
for (const key of Object.keys(params)) {
|
|
207
|
+
bag[key] = () => clientPage.value.params[key]
|
|
208
|
+
}
|
|
209
|
+
if (hasChild) {
|
|
210
|
+
bag.$children = CHILD_PRESENT
|
|
211
|
+
}
|
|
212
|
+
return bag
|
|
213
|
+
}
|
|
195
214
|
let boundary = baseBoundary(index)
|
|
196
215
|
for (let depth = index; depth < layoutViews.length; depth += 1) {
|
|
197
216
|
const view = layoutViews[depth] as Route
|
|
@@ -200,14 +219,15 @@ export function router(
|
|
|
200
219
|
boundary.open,
|
|
201
220
|
boundary.close,
|
|
202
221
|
view.build,
|
|
203
|
-
|
|
222
|
+
/* A layout always has a child below: a deeper layout, or the page. */
|
|
223
|
+
propsBag(depth < layoutViews.length - 1 || pageView !== undefined),
|
|
204
224
|
/* The layout's route key names its scope in the inspector's Reactive tab
|
|
205
225
|
(no host element to read a tag from — see `scopeLabel`). */
|
|
206
226
|
chainKeys[depth],
|
|
207
227
|
)
|
|
208
228
|
const slot = PENDING_OUTLET.current
|
|
209
229
|
if (slot === undefined) {
|
|
210
|
-
throw new Error('[abide] a layout.abide must contain a
|
|
230
|
+
throw new Error('[abide] a layout.abide must contain a {children()} outlet')
|
|
211
231
|
}
|
|
212
232
|
mountedLayouts.push({ key: chainKeys[depth] as string, dispose, slot })
|
|
213
233
|
boundary = slot
|
|
@@ -220,7 +240,8 @@ export function router(
|
|
|
220
240
|
boundary.open,
|
|
221
241
|
boundary.close,
|
|
222
242
|
pageView.build,
|
|
223
|
-
|
|
243
|
+
/* A page is a leaf — no child layer. */
|
|
244
|
+
propsBag(false),
|
|
224
245
|
/* The page's route key names its scope in the inspector (see above). */
|
|
225
246
|
pageKey,
|
|
226
247
|
).dispose
|
|
@@ -41,7 +41,7 @@ const keyFor = (url: string): string => `abide:outbox:${url}`
|
|
|
41
41
|
/*
|
|
42
42
|
A durable, app-owned FIFO retry queue for one RPC. A call fetches directly and throws as
|
|
43
43
|
before while the queue is empty; when the server can't be reached — a transport failure or
|
|
44
|
-
a 502/503/504/
|
|
44
|
+
a 502/503/504/520–527/530 — the caller `park`s the request here (a side-effect; the call still
|
|
45
45
|
throws), and once a backlog exists the caller parks every later call at the tail too, so
|
|
46
46
|
FIFO order is preserved on replay. Built on `doc` + `persist`, so the queue IS a persisted
|
|
47
47
|
document that survives a reload. Draining is manual — the app calls `retry()` (per-entry or
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/*
|
|
2
|
+
A no-op truthiness marker the router (client) and `renderChain` (SSR) set as a layout's
|
|
3
|
+
`$props.$children` when a child layer (a nested layout or the page) exists below it. Read
|
|
4
|
+
only by `{#if children}` (→ `$props?.$children`); a layout's `{children()}` lowers to the
|
|
5
|
+
`outlet()` boundary the router fills, so this value is never invoked — it exists purely so
|
|
6
|
+
`{#if children}` reads a uniform presence signal on both the client and the server.
|
|
7
|
+
*/
|
|
8
|
+
export const CHILD_PRESENT = (_host: Element): void => {}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
`<abide-outlet>` ELEMENT, so the next chain layer the router fills in lays out as a true
|
|
3
3
|
direct child of the slot's parent (no wrapper box breaking the layout's flex/grid/`:first-child`).
|
|
4
4
|
|
|
5
|
-
`abide:outlet` / `/abide:outlet` deliberately match `
|
|
6
|
-
(the `abide:` / `/abide:` convention), so a layout's own hole-scanning treats the outlet's
|
|
5
|
+
`abide:outlet` / `/abide:outlet` deliberately match `markerDepthDelta`'s `startsWith('abide:')` /
|
|
6
|
+
`startsWith('/abide:')` rule (the `abide:` / `/abide:` convention), so a layout's own hole-scanning treats the outlet's
|
|
7
7
|
future child content as a balanced range and skips it — exactly like an `await`/`try` boundary.
|
|
8
8
|
The router fills the boundary with the next layer (see `outlet`/`fillBoundary`). */
|
|
9
9
|
export const OUTLET_OPEN = 'abide:outlet'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* The comment-marker "wire alphabet" — the single source of truth for the sentinel
|
|
2
2
|
strings the SSR emit (`generateSSR`) writes into HTML comments and the hydrate scan
|
|
3
|
-
(`skeleton`) + every range-mount runtime (`when`/`
|
|
3
|
+
(`skeleton`) + every range-mount runtime (`when`/`switchBlock`/`each`/`mountRange`/
|
|
4
4
|
`mountSlot`/`appendSnippet`) creates as `document.createComment` nodes. Both sides
|
|
5
5
|
reference THESE constants, so a marker the server writes and the marker the client
|
|
6
6
|
looks for can never drift on a literal.
|
|
@@ -2,13 +2,19 @@ import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
Mutable singletons shared by every reactive primitive: the observer currently
|
|
5
|
-
running (so a read can register itself as that observer's dependency), the
|
|
5
|
+
running (so a read can register itself as that observer's dependency), the queue
|
|
6
6
|
of effects dirtied since the last flush, and the batch depth (writes inside a
|
|
7
7
|
batch queue effects and flush once on exit). Held on one object so signal,
|
|
8
8
|
computed, and effect all reference the same graph state without a barrel.
|
|
9
|
+
|
|
10
|
+
`pendingEffects` is a plain array, not a `Set`: an effect is enqueued only on its
|
|
11
|
+
CLEAN→dirty transition (`mark`'s `wasClean` gate), so it can be pushed at most once
|
|
12
|
+
per flush cycle — the status machine already guarantees the dedup a `Set` would,
|
|
13
|
+
without hashing every enqueue. The flush double-buffers (swaps in a fresh array) so
|
|
14
|
+
effects re-dirtied mid-flush queue for the next pass.
|
|
9
15
|
*/
|
|
10
16
|
export const REACTIVE_CONTEXT: {
|
|
11
17
|
observer: ReactiveNode | undefined
|
|
12
|
-
pendingEffects:
|
|
18
|
+
pendingEffects: ReactiveNode[]
|
|
13
19
|
batchDepth: number
|
|
14
|
-
} = { observer: undefined, pendingEffects:
|
|
20
|
+
} = { observer: undefined, pendingEffects: [], batchDepth: 0 }
|
|
@@ -6,7 +6,7 @@ Runs a component `build` and returns its result (the mount handle / disposer)
|
|
|
6
6
|
alongside the component's own `model` document — the serializable `state` doc,
|
|
7
7
|
needed so a hot swap can carry its value across (see `hotReplace`). The model is
|
|
8
8
|
found, not threaded: a component seeds its `model` first (the desugared
|
|
9
|
-
`const model =
|
|
9
|
+
`const $$model = scope()` + its init `replace` patches (state slots lowered by `lowerDocAccess`) run before any child mounts or
|
|
10
10
|
control-flow blocks), so the FIRST patch announced on the bus during the build names
|
|
11
11
|
it. A component with no `state` mints no model and emits nothing first — `model` is
|
|
12
12
|
then `undefined` and there is nothing to preserve. Used only on the hot path; the
|
|
@@ -118,7 +118,11 @@ export function createDoc(initial: unknown): Doc {
|
|
|
118
118
|
and this very descend scan degrades linearly with it. The woken
|
|
119
119
|
reader re-mints a fresh node on its flush if the path ever returns.
|
|
120
120
|
Deleting the current entry mid-iteration is safe on a Map. */
|
|
121
|
-
|
|
121
|
+
/* Walk from the already-resolved container (`rootValue`) using only the
|
|
122
|
+
path SUFFIX past the shared prefix, instead of re-walking every
|
|
123
|
+
candidate's full path from the tree root — the prefix is walked once
|
|
124
|
+
per wake, not once per descendant. */
|
|
125
|
+
const walk = walkPath(rootValue, candidate.slice(prefix.length))
|
|
122
126
|
if (walk.exists) {
|
|
123
127
|
writeNode(node, walk.value)
|
|
124
128
|
} else {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { NODE_STATE } from './NODE_STATE.ts'
|
|
2
2
|
import { OWNER } from './OWNER.ts'
|
|
3
|
-
import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
|
|
4
3
|
import { runNode } from './runNode.ts'
|
|
5
4
|
import { toTeardown } from './toTeardown.ts'
|
|
6
5
|
import type { EffectResult } from './types/EffectResult.ts'
|
|
@@ -52,11 +51,12 @@ export function createEffectNode(fn: () => EffectResult): () => void {
|
|
|
52
51
|
runCleanup()
|
|
53
52
|
unlinkDeps(node)
|
|
54
53
|
/* Clearing compute makes runNode a no-op: an effect disposed mid-flush (by an
|
|
55
|
-
earlier effect in the same batch) is still in flushEffects
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
earlier effect in the same batch) is still in the queue flushEffects is
|
|
55
|
+
draining, so without this it would re-run its body and re-link into the graph
|
|
56
|
+
— a disposed effect resurrected. This alone neutralizes it; the queue is a
|
|
57
|
+
plain array (no O(1) delete) and a settled no-op iteration is cheaper than an
|
|
58
|
+
O(n) splice, so the stale entry is left to be skipped on its flush. */
|
|
58
59
|
node.compute = undefined
|
|
59
|
-
REACTIVE_CONTEXT.pendingEffects.delete(node)
|
|
60
60
|
}
|
|
61
61
|
if (OWNER.current !== undefined) {
|
|
62
62
|
OWNER.current.push(dispose)
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { REACTIVE_CONTEXT } from './REACTIVE_CONTEXT.ts'
|
|
2
|
+
import type { ReactiveNode } from './types/ReactiveNode.ts'
|
|
2
3
|
import { updateIfNecessary } from './updateIfNecessary.ts'
|
|
3
4
|
|
|
4
5
|
/*
|
|
5
6
|
Drains queued effects synchronously. Each is queued when it first leaves CLEAN, but
|
|
6
7
|
runs only if `updateIfNecessary` finds a dependency that truly changed — a CHECK
|
|
7
8
|
effect whose deps all memoised back to equal values settles to CLEAN without
|
|
8
|
-
running its body.
|
|
9
|
-
|
|
10
|
-
mid-iteration; loops until the
|
|
9
|
+
running its body. Double-buffers the queue each pass — swaps in a fresh array and
|
|
10
|
+
drains the captured one — so an effect that dirties further effects re-queues them
|
|
11
|
+
for the next pass rather than mutating the array mid-iteration; loops until the
|
|
12
|
+
graph settles. The swap reuses the drained array as the next spare, so a steady
|
|
13
|
+
flush allocates nothing.
|
|
11
14
|
*/
|
|
12
15
|
export function flushEffects(): void {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
/* Empty-queue fast path allocates nothing — only the length check, matching the
|
|
17
|
+
old `Set.size` guard. Hot: a write with no subscribed effect (e.g. a reconcile's
|
|
18
|
+
index-cell write nobody reads) still calls here at batch-exit, thousands of times
|
|
19
|
+
per list op; the spare array below must not be born on that path. */
|
|
20
|
+
if (REACTIVE_CONTEXT.pendingEffects.length === 0) {
|
|
21
|
+
return
|
|
19
22
|
}
|
|
23
|
+
let spare: ReactiveNode[] = []
|
|
24
|
+
do {
|
|
25
|
+
const batch = REACTIVE_CONTEXT.pendingEffects
|
|
26
|
+
REACTIVE_CONTEXT.pendingEffects = spare
|
|
27
|
+
for (let index = 0; index < batch.length; index += 1) {
|
|
28
|
+
updateIfNecessary(batch[index] as ReactiveNode)
|
|
29
|
+
}
|
|
30
|
+
batch.length = 0
|
|
31
|
+
spare = batch
|
|
32
|
+
} while (REACTIVE_CONTEXT.pendingEffects.length > 0)
|
|
20
33
|
}
|
|
@@ -2,7 +2,7 @@ import type { AbideHistoryState } from './types/AbideHistoryState.ts'
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
Per-history-entry scroll buckets for manual scroll restoration. The browser's own
|
|
5
|
-
restoration is disabled (`history.scrollRestoration = 'manual'`
|
|
5
|
+
restoration is disabled (`history.scrollRestoration = 'manual'` after the first build/hydration)
|
|
6
6
|
because the router tears the page down and rebuilds it AFTER the browser would have
|
|
7
7
|
restored scroll — so the offset is lost against a node that no longer exists. Instead
|
|
8
8
|
each history entry carries a monotonic id (stamped into `history.state.abideEntry` by
|
|
@@ -15,8 +15,10 @@ observer is reading the same source in the same order as before — advance the
|
|
|
15
15
|
cursor and reuse the edge, allocating nothing. Otherwise splice a fresh edge in at
|
|
16
16
|
the cursor; `runNode` trims whatever stale edges trail the cursor when compute ends.
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
On re-runs, an edge whose `dep` matches the next slot in the established list is
|
|
19
|
+
reused in-place (cursor advances, no allocation). New edges are still appended for
|
|
20
|
+
reads that extend the list or find a mismatched slot — including a second consecutive
|
|
21
|
+
read of the same source when no matching edge yet follows the cursor.
|
|
20
22
|
*/
|
|
21
23
|
export function track(dep: ReactiveNode): void {
|
|
22
24
|
const sub = REACTIVE_CONTEXT.observer
|
|
@@ -15,27 +15,41 @@ reaches). `nextSub` is read before recursing so the walk holds no edge a deeper
|
|
|
15
15
|
pass could detach.
|
|
16
16
|
*/
|
|
17
17
|
function mark(node: ReactiveNode, status: number): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
if (
|
|
25
|
-
|
|
18
|
+
/* Tail-iterative down the subscriber cone: a node with a single subscriber (a
|
|
19
|
+
linear computed chain) advances by reassigning `node` instead of recursing, so a
|
|
20
|
+
deep chain costs a loop, not a 500-frame call stack. Only a true fan-out (>1
|
|
21
|
+
subscriber) recurses, and only for the extra branches. */
|
|
22
|
+
let nextStatus = status
|
|
23
|
+
for (;;) {
|
|
24
|
+
if (node.status >= nextStatus) {
|
|
25
|
+
return
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
27
|
+
const wasClean = node.status === NODE_STATE.CLEAN
|
|
28
|
+
node.status = nextStatus
|
|
29
|
+
if (node.isEffect) {
|
|
30
|
+
if (wasClean) {
|
|
31
|
+
REACTIVE_CONTEXT.pendingEffects.push(node)
|
|
32
|
+
}
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
/* A computed propagates CHECK to its subscribers only on its first move out of
|
|
36
|
+
CLEAN — they are already CHECK on any later upgrade, so re-walking is wasted. */
|
|
37
|
+
if (!wasClean) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
const head = node.subsHead
|
|
41
|
+
if (head === undefined) {
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
/* Recurse into every branch but the last; continue the loop on the last, so the
|
|
45
|
+
common single-subscriber chain never recurses. */
|
|
46
|
+
let link = head
|
|
47
|
+
while (link.nextSub !== undefined) {
|
|
48
|
+
mark(link.sub, NODE_STATE.CHECK)
|
|
49
|
+
link = link.nextSub
|
|
50
|
+
}
|
|
51
|
+
node = link.sub
|
|
52
|
+
nextStatus = NODE_STATE.CHECK
|
|
39
53
|
}
|
|
40
54
|
}
|
|
41
55
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/* A routable page/layout. Callable to mount directly into a host (the direct-mount
|
|
2
2
|
API), but the router instead uses `build` — the bare client build — to fill the
|
|
3
3
|
layer into its outlet boundary as a marker range (no `<abide-outlet>` wrapper; see
|
|
4
|
-
`fillBoundary`/`outlet`). `hydratable` (
|
|
5
|
-
tells the router whether the first paint adopts the SSR DOM in place. */
|
|
4
|
+
`fillBoundary`/`outlet`). `hydratable` (always true for compiled components; undefined on manually-constructed
|
|
5
|
+
Route objects) tells the router whether the first paint adopts the SSR DOM in place. */
|
|
6
6
|
export type Route = ((host: Element, props?: unknown) => (() => void) | undefined) & {
|
|
7
7
|
build: (host: Node, props?: unknown) => void
|
|
8
8
|
hydratable?: boolean
|
|
@@ -10,8 +10,9 @@ page/route registries carry — abide-ui's compiled-component shape.
|
|
|
10
10
|
export type UiComponent = ((host: Element, props?: UiProps) => () => void) & {
|
|
11
11
|
/* `ctx` is the request-local block-id counter, threaded so a child's ids share the
|
|
12
12
|
page's depth-first numbering; omitted at the top level (a fresh counter defaults
|
|
13
|
-
in). Returns a Promise when the component
|
|
14
|
-
… then}` block
|
|
13
|
+
in). Returns a Promise when the component contains an inline `await` — a blocking
|
|
14
|
+
`{#await … then}` block, a child-component render, a `<slot>` read, or a
|
|
15
|
+
top-level `await` in the author script; otherwise renders synchronously. Callers `await`
|
|
15
16
|
it either way (awaiting a sync value just returns it). */
|
|
16
17
|
render: (props?: UiProps, ctx?: RenderContext) => SsrRender | Promise<SsrRender>
|
|
17
18
|
hydrate?: (host: Element, props?: UiProps) => () => void
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
/*
|
|
2
|
-
What a component is invoked with.
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
it is handed.
|
|
2
|
+
What a component is invoked with. A top-level page/layout is called by the router
|
|
3
|
+
(client) and `renderChain` (SSR) with its route params as reactive thunks, shaped
|
|
4
|
+
identically to the thunk map `mountChild` passes a nested child — so `props()` reads
|
|
5
|
+
(`$props[name]?.()`) work uniformly on both. `$children` is the slot builder a parent
|
|
6
|
+
component passes (carrying the component's `{children()}` content), or `CHILD_PRESENT`
|
|
7
|
+
the router/SSR set on a layout that has a child layer below it.
|
|
9
8
|
*/
|
|
10
|
-
export type UiProps =
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
$children?: (host: Element) => void
|
|
14
|
-
})
|
|
9
|
+
export type UiProps = Record<string, (() => unknown) | ((host: Element) => void)> & {
|
|
10
|
+
$children?: (host: Element) => void
|
|
11
|
+
}
|
|
@@ -17,13 +17,25 @@ export function walkPath(tree: unknown, path: string): PathWalk {
|
|
|
17
17
|
if (path === '') {
|
|
18
18
|
return { exists: tree !== undefined, value: tree }
|
|
19
19
|
}
|
|
20
|
+
/* A path with no `~` carries no escape in any segment, so the per-segment
|
|
21
|
+
unescape is skipped wholesale — the case for every plain-identifier path. */
|
|
22
|
+
const escaped = path.includes('~')
|
|
20
23
|
let current: unknown = tree
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
/* Scan `/`-separated segments by index, no `split` array allocated — this runs
|
|
25
|
+
once per candidate on the structural descend scan, the hot path's hot loop. */
|
|
26
|
+
let start = 0
|
|
27
|
+
for (;;) {
|
|
28
|
+
const slash = path.indexOf('/', start)
|
|
29
|
+
const end = slash === -1 ? path.length : slash
|
|
30
|
+
const raw = path.slice(start, end)
|
|
31
|
+
const segment = escaped ? unescapeKey(raw) : raw
|
|
23
32
|
if (current === null || typeof current !== 'object' || !(segment in current)) {
|
|
24
33
|
return { exists: false, value: undefined }
|
|
25
34
|
}
|
|
26
35
|
current = (current as Record<string, unknown>)[segment]
|
|
36
|
+
if (slash === -1) {
|
|
37
|
+
return { exists: true, value: current }
|
|
38
|
+
}
|
|
39
|
+
start = slash + 1
|
|
27
40
|
}
|
|
28
|
-
return { exists: true, value: current }
|
|
29
41
|
}
|
|
@@ -1,26 +1,41 @@
|
|
|
1
1
|
import { createCacheStore } from '../shared/createCacheStore.ts'
|
|
2
|
+
import { healthSeedSlot } from '../shared/healthSeedSlot.ts'
|
|
3
|
+
import { rpcTimeoutSlot } from '../shared/rpcTimeoutSlot.ts'
|
|
4
|
+
import { setAppName } from '../shared/setAppName.ts'
|
|
2
5
|
import { setBaseResolver } from '../shared/setBaseResolver.ts'
|
|
3
6
|
import { setCacheStoreResolver } from '../shared/setCacheStoreResolver.ts'
|
|
4
7
|
import { setGlobalCacheStoreResolver } from '../shared/setGlobalCacheStoreResolver.ts'
|
|
5
8
|
import { setPageResolver } from '../shared/setPageResolver.ts'
|
|
6
9
|
import type { CacheSnapshotEntry } from '../shared/types/CacheSnapshotEntry.ts'
|
|
7
10
|
import type { StreamedResolution } from '../shared/types/StreamedResolution.ts'
|
|
8
|
-
import { installHotBridge } from './installHotBridge.ts'
|
|
9
|
-
import { installInspectorBridge } from './installInspectorBridge.ts'
|
|
10
11
|
import { probeNavigation } from './probeNavigation.ts'
|
|
12
|
+
|
|
13
|
+
/* Build-time flag the production client defines false (see build.ts `define`) so the dev-only
|
|
14
|
+
hot bridge — and the entire DOM runtime it statically pulls in for re-builds — is dead-code-
|
|
15
|
+
eliminated, not even emitted as a chunk. Dev defines it true; the test preload sets it on
|
|
16
|
+
globalThis so the bare reference resolves there. */
|
|
17
|
+
declare const __ABIDE_DEV__: boolean
|
|
18
|
+
|
|
11
19
|
import { router } from './router.ts'
|
|
12
20
|
import { clientPage } from './runtime/clientPage.ts'
|
|
13
21
|
import type { RouteLoader } from './runtime/types/RouteLoader.ts'
|
|
14
22
|
import { seedResolved } from './seedResolved.ts'
|
|
15
23
|
|
|
16
24
|
/* The server's __SSR__ payload this entry consumes. */
|
|
17
|
-
type SsrPayload = {
|
|
25
|
+
type SsrPayload = {
|
|
26
|
+
cache?: CacheSnapshotEntry[]
|
|
27
|
+
base?: string
|
|
28
|
+
app?: string
|
|
29
|
+
health?: Record<string, unknown>
|
|
30
|
+
clientTimeout?: number
|
|
31
|
+
}
|
|
18
32
|
|
|
19
33
|
/*
|
|
20
34
|
The official abide-ui client entry. Reads the server's `window.__SSR__` payload,
|
|
21
35
|
seeds a tab-scoped cache store from the inline snapshot (so a warm `cache()` read
|
|
22
|
-
resolves synchronously and the matching
|
|
23
|
-
no re-fetch), installs the mount base,
|
|
36
|
+
resolves synchronously and the matching `{#await}` adopts the SSR DOM with
|
|
37
|
+
no re-fetch), installs the mount base, seeds the per-page __SSR__ stamps (app
|
|
38
|
+
name, health, RPC timeout), and starts the router — which imports the
|
|
24
39
|
current route's chunk, adopts the server-rendered `#app`, then drives SPA
|
|
25
40
|
navigation — importing each further page's chunk on first visit and probing the
|
|
26
41
|
destination through the server's app.handle so auth/redirect gating still applies.
|
|
@@ -37,19 +52,31 @@ export function startClient(
|
|
|
37
52
|
if (target === null) {
|
|
38
53
|
throw new Error('[abide] startClient: missing #app target')
|
|
39
54
|
}
|
|
40
|
-
/* Dev only: the live-reload script sets `__abideDev` before this module runs,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
55
|
+
/* Dev only: the live-reload script sets `__abideDev` before this module runs, so the
|
|
56
|
+
runtime bridge is in place before any component mounts and records its instances
|
|
57
|
+
(mountChild) for hot replacement. Lazy-imported and `__ABIDE_DEV__`-gated so the
|
|
58
|
+
bridge (and the DOM runtime it drags in) is fully dead-code-eliminated in production
|
|
59
|
+
rather than shipped behind a runtime flag the minifier can't prove false. */
|
|
60
|
+
if (__ABIDE_DEV__ && (globalThis as { __abideDev?: boolean }).__abideDev) {
|
|
61
|
+
import('./installHotBridge.ts').then((module) => module.installHotBridge())
|
|
45
62
|
}
|
|
46
|
-
/* Inspector only: the server injects `__abideInspect` when ABIDE_ENABLE_INSPECTOR
|
|
47
|
-
|
|
63
|
+
/* Inspector only: the server injects `__abideInspect` when ABIDE_ENABLE_INSPECTOR is on,
|
|
64
|
+
so the scope/router bridge arms before the router builds any scope. Inspector can be
|
|
65
|
+
enabled in production, so this stays a lazy chunk (not `__ABIDE_DEV__`-gated) — emitted
|
|
66
|
+
but fetched only when the flag is set, never weighing down the default client load. */
|
|
48
67
|
if ((globalThis as { __abideInspect?: boolean }).__abideInspect) {
|
|
49
|
-
installInspectorBridge()
|
|
68
|
+
import('./installInspectorBridge.ts').then((module) => module.installInspectorBridge())
|
|
50
69
|
}
|
|
51
70
|
const ssr = (globalThis as { __SSR__?: SsrPayload }).__SSR__ ?? {}
|
|
52
71
|
setBaseResolver(() => ssr.base ?? '')
|
|
72
|
+
/* Seed the per-page __SSR__ stamps into their shared slots before mount: the app
|
|
73
|
+
name (default log channel), the health payload (so health()'s first probe is warm),
|
|
74
|
+
and the env-configured RPC timeout (ABIDE_CLIENT_TIMEOUT, shipped per request).
|
|
75
|
+
Without this the browser falls back to channel 'app', a cold first health probe,
|
|
76
|
+
and unbounded RPC fetches. */
|
|
77
|
+
setAppName(ssr.app)
|
|
78
|
+
healthSeedSlot.payload = ssr.health
|
|
79
|
+
rpcTimeoutSlot.ms = ssr.clientTimeout
|
|
53
80
|
/* The `page` proxy reads route/params/url off the router-updated snapshot. */
|
|
54
81
|
setPageResolver(() => clientPage.value)
|
|
55
82
|
|
|
@@ -61,7 +88,7 @@ export function startClient(
|
|
|
61
88
|
(inline — reads settled at render-return, in __SSR__) and `__abideResumeCache` (pending
|
|
62
89
|
{#await} reads whose `__abideResolve(...)` chunks the stream pushed during parse, before
|
|
63
90
|
this deferred bundle ran). A warm entry lets a `cache()` read resolve synchronously so
|
|
64
|
-
|
|
91
|
+
`{#await}` adopts without a refetch; a miss marker re-fetches live. */
|
|
65
92
|
const streamed =
|
|
66
93
|
(globalThis as { __abideResumeCache?: StreamedResolution[] }).__abideResumeCache ?? []
|
|
67
94
|
for (const resolution of [...(ssr.cache ?? []), ...streamed]) {
|
package/src/lib/ui/state.ts
CHANGED
|
@@ -7,9 +7,9 @@ import { writeNode } from './runtime/writeNode.ts'
|
|
|
7
7
|
A writable reactive cell — abide's from-scratch reactive primitive, with no
|
|
8
8
|
compiler sigil and no external reactivity-library import. `.value` is a
|
|
9
9
|
plain getter/setter over a signal node, so a read/write shows up as exactly that
|
|
10
|
-
in a stack trace. The compiler
|
|
11
|
-
|
|
12
|
-
needs no magic.
|
|
10
|
+
in a stack trace. The compiler enforces scope attachment (bare `state()` is a compile
|
|
11
|
+
error — use `scope().state()`), desugars plain `state(initial)` to a serializable `model`
|
|
12
|
+
doc slot, and keeps `state(initial, transform)` as a `.value` cell; the runtime needs no magic.
|
|
13
13
|
|
|
14
14
|
`transform` is an optional coercion gate on the write path: every `.value =`
|
|
15
15
|
runs it and stores what it returns, with `previous` for clamp-relative writes or
|
|
@@ -17,9 +17,9 @@ methods so a scope is a passable value: `<Child parentScope={scope} />`.
|
|
|
17
17
|
Capabilities route where the scope's changes go: `record()` to an undo journal,
|
|
18
18
|
`persist()` to durable storage, `broadcast()` to peers — declared once, then
|
|
19
19
|
`undo`/`redo` act on a recorded scope. `id` is the scope's identity for the
|
|
20
|
-
boundary-crossing capabilities — `persist()` defaults its key to it. `scope()` is
|
|
21
|
-
|
|
22
|
-
`history`/`persist`/`sync` helpers it composes are internal
|
|
20
|
+
boundary-crossing capabilities — `persist()` defaults its key to it. `scope()` is the only user-facing public entry; `effect` is also exported
|
|
21
|
+
but only for generated binding code and the SSR strip (tagged plumbing). The
|
|
22
|
+
`history`/`persist`/`sync` helpers it composes are internal.
|
|
23
23
|
*/
|
|
24
24
|
export type Scope = {
|
|
25
25
|
readonly id: string
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { BunPlugin } from 'bun'
|
|
2
2
|
import { abideResolverPlugin } from './abideResolverPlugin.ts'
|
|
3
3
|
import { abideUiPlugin } from './lib/ui/compile/abideUiPlugin.ts'
|
|
4
|
+
import { zodCjsPlugin } from './zodCjsPlugin.ts'
|
|
4
5
|
|
|
5
6
|
/*
|
|
6
|
-
The server-target Bun.build plugin
|
|
7
|
-
the abide-ui `.abide` loader (so SSR `render()` resolves)
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
The server-target Bun.build plugin set shared by compile / buildCli / bundleApp:
|
|
8
|
+
the abide-ui `.abide` loader (so SSR `render()` resolves), abide's virtual-module
|
|
9
|
+
resolver, and the zod→CommonJS shim that works around bun's broken ESM-cycle
|
|
10
|
+
bundling (see zodCjsPlugin). `embedAssets` flips on the gzip asset embed used by
|
|
11
|
+
the standalone server binary; the CLI + launcher builds leave it off.
|
|
10
12
|
*/
|
|
11
13
|
export function serverBuildPlugins({
|
|
12
14
|
cwd,
|
|
@@ -15,5 +17,9 @@ export function serverBuildPlugins({
|
|
|
15
17
|
cwd: string
|
|
16
18
|
embedAssets?: boolean
|
|
17
19
|
}): BunPlugin[] {
|
|
18
|
-
return [
|
|
20
|
+
return [
|
|
21
|
+
zodCjsPlugin(cwd),
|
|
22
|
+
abideUiPlugin,
|
|
23
|
+
abideResolverPlugin({ cwd, embedAssets, target: 'server' }),
|
|
24
|
+
]
|
|
19
25
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname } from 'node:path'
|
|
3
|
+
import type { BunPlugin } from 'bun'
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Routes every `zod` import in a server bundle to zod's CommonJS build.
|
|
7
|
+
|
|
8
|
+
zod v4's ESM is reached through two subpaths (`zod` and `zod/v4/core`) whose
|
|
9
|
+
modules form an initialization cycle. Bun's bundler emits a broken init order
|
|
10
|
+
for that cycle (oven-sh/bun#31586, closed as not-planned), so a bundled binary
|
|
11
|
+
crashes (`<helper> is not defined`) or — for a lazily imported module — silently
|
|
12
|
+
fails to evaluate, which drops every rpc whose module imports zod from the
|
|
13
|
+
registry. It only bites bundled output; `abide dev` runs unbundled and is fine.
|
|
14
|
+
|
|
15
|
+
zod ships a parallel CommonJS build (`index.cjs`, `v4/core/index.cjs`, …), and
|
|
16
|
+
CommonJS resolves circular requires through a mutating `module.exports` with no
|
|
17
|
+
temporal-dead-zone, so the cycle initializes correctly. Resolving each zod
|
|
18
|
+
specifier to its `.cjs` sibling makes bun bundle zod as CommonJS and sidesteps
|
|
19
|
+
the bug while keeping zod v4. Server-only: the client bundle strips zod from rpc
|
|
20
|
+
modules (they become remote proxies), and dev never bundles.
|
|
21
|
+
*/
|
|
22
|
+
export function zodCjsPlugin(cwd: string): BunPlugin {
|
|
23
|
+
return {
|
|
24
|
+
name: 'abide-zod-cjs',
|
|
25
|
+
setup(build) {
|
|
26
|
+
build.onResolve({ filter: /^zod($|\/)/ }, (args) => {
|
|
27
|
+
/* Resolve from the importer (or cwd for the entry) to zod's ESM
|
|
28
|
+
target, then swap to its `.cjs` sibling when one exists. */
|
|
29
|
+
const from = args.importer ? dirname(args.importer) : cwd
|
|
30
|
+
const resolved = Bun.resolveSync(args.path, from)
|
|
31
|
+
const cjs = resolved.replace(/\.js$/, '.cjs')
|
|
32
|
+
return { path: existsSync(cjs) ? cjs : resolved }
|
|
33
|
+
})
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"auto_install_extensions": { "abide": true },
|
|
3
|
-
"semantic_tokens": "combined"
|
|
3
|
+
"semantic_tokens": "combined",
|
|
4
|
+
"global_lsp_settings": {
|
|
5
|
+
"semantic_token_rules": [
|
|
6
|
+
{ "token_type": "tag", "style": ["tag"] },
|
|
7
|
+
{ "token_type": "attribute", "style": ["attribute"] }
|
|
8
|
+
]
|
|
9
|
+
}
|
|
4
10
|
}
|