@abide/abide 0.105.1 → 0.107.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/CHANGELOG.md +28 -0
- package/bin/abide.ts +4 -1
- package/docs/REGISTRY.md +156 -44
- package/docs/RULEBOOK.md +392 -88
- package/docs/abide.md +7245 -10248
- package/package.json +4 -1
- package/scaffold/AGENTS.md +2 -0
- package/scaffold/src/getGreeting.ts +6 -0
- package/scaffold/src/index.abide +3 -1
- package/scaffold/src/tests/getGreeting.test.ts +6 -0
- package/scaffold/src/tests/index.test.ts +18 -0
- package/src/compiler/DOCS_SITE.ts +2 -0
- package/src/compiler/Lowering.ts +234 -14
- package/src/compiler/PositionTable.ts +14 -7
- package/src/compiler/PositionedRefusal.ts +13 -0
- package/src/compiler/Refusals.ts +18 -11
- package/src/compiler/SHARED_NAMES.ts +2 -0
- package/src/compiler/ShadowMap.ts +37 -0
- package/src/compiler/TEMPLATE_BLOCKS.ts +12 -0
- package/src/compiler/TypeSession.ts +410 -31
- package/src/compiler/appLayout.ts +18 -2
- package/src/compiler/argsSchemaOf.ts +55 -9
- package/src/compiler/checkApp.ts +33 -0
- package/src/compiler/checkedShadow.ts +36 -22
- package/src/compiler/citedClauses.ts +14 -0
- package/src/compiler/classifyRead.ts +6 -0
- package/src/compiler/classifyTemplate.ts +7 -4
- package/src/compiler/compileApp.ts +2 -2
- package/src/compiler/declaredExports.ts +27 -6
- package/src/compiler/emitClient.ts +306 -103
- package/src/compiler/handlersUnder.ts +4 -0
- package/src/compiler/invocationProps.ts +1 -1
- package/src/compiler/isIgnored.ts +27 -0
- package/src/compiler/parseAbide.ts +176 -76
- package/src/compiler/projectShadow.ts +30 -6
- package/src/compiler/readSources.ts +1 -1
- package/src/compiler/readsIn.ts +100 -1
- package/src/compiler/renderMarkup.ts +36 -5
- package/src/compiler/scaffold.ts +19 -6
- package/src/compiler/serveLanguage.ts +156 -21
- package/src/compiler/templateContext.ts +150 -0
- package/src/compiler/tokenize.ts +6 -0
- package/src/server/BRIDGE_VERSION.ts +1 -1
- package/src/server/Inbox.ts +8 -0
- package/src/server/MCP_PROTOCOL_VERSION.ts +1 -1
- package/src/server/MOUNTABLE.ts +6 -0
- package/src/server/abideModules.ts +8 -1
- package/src/server/answerRequest.ts +3 -1
- package/src/server/answerRpc.ts +26 -12
- package/src/server/answerSocket.ts +26 -1
- package/src/server/argsFor.ts +4 -3
- package/src/server/assistant.ts +21 -14
- package/src/server/bridgeRelay.ts +108 -20
- package/src/server/claudeApi.ts +19 -11
- package/src/server/claudeCode.ts +19 -14
- package/src/server/commands.ts +73 -9
- package/src/server/createApp.ts +41 -7
- package/src/server/devWorker.ts +3 -3
- package/src/server/generatedSurfaces.ts +37 -4
- package/src/server/muxUpgrade.ts +47 -0
- package/src/server/preload.ts +13 -2
- package/src/server/render.ts +4 -1
- package/src/server/runCompiled.ts +1 -0
- package/src/server/seedBuffer.ts +31 -1
- package/src/server/serveDev.ts +83 -72
- package/src/server/surfaceOf.ts +3 -0
- package/src/server/testMount.ts +53 -0
- package/src/shared/ADDRESSES.ts +1 -1
- package/src/shared/ERROR_META.ts +3 -0
- package/src/shared/Schema.ts +1 -1
- package/src/shared/WORK.ts +14 -12
- package/src/shared/applyPatch.ts +1 -1
- package/src/shared/boundValue.ts +81 -0
- package/src/shared/index.ts +7 -2
- package/src/shared/jsonSchema.ts +25 -2
- package/src/shared/log.ts +11 -1
- package/src/shared/reactive/Keyed.ts +30 -6
- package/src/shared/reactive/PATCHES.ts +1 -1
- package/src/shared/reactive/Reactive.ts +118 -60
- package/src/shared/reactive/canonicalKey.ts +1 -1
- package/src/shared/reactive/index.ts +13 -5
- package/src/shared/reactive/recordWrites.ts +1 -1
- package/src/shared/reactive/watch.ts +26 -4
- package/src/shared/rpc.ts +6 -1
- package/src/shared/url.ts +7 -3
- package/src/shared/validateJson.ts +1 -1
- package/src/ui/ArmRegion.ts +2 -2
- package/src/ui/List.ts +9 -9
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +194 -36
- package/src/ui/Scope.ts +29 -0
- package/src/ui/adoptWalk.ts +3 -3
- package/src/ui/applyHead.ts +25 -3
- package/src/ui/bindElement.ts +4 -0
- package/src/ui/bindProperty.ts +70 -22
- package/src/ui/index.ts +1 -1
- package/src/ui/listenForRoutes.ts +14 -3
- package/src/ui/reconcile.ts +23 -28
- package/src/ui/regions.ts +9 -2
- package/src/ui/rows.ts +108 -28
|
@@ -27,7 +27,12 @@ import { decodeCharacterReferences } from './decodeCharacterReferences.ts'
|
|
|
27
27
|
import { moduleImports } from './emitComponent.ts'
|
|
28
28
|
import { invocationProps, passedName } from './invocationProps.ts'
|
|
29
29
|
import { isNamedAlone } from './isNamedAlone.ts'
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
type ItemUses,
|
|
32
|
+
type LoweredChunk,
|
|
33
|
+
Lowering,
|
|
34
|
+
PROPS,
|
|
35
|
+
} from './Lowering.ts'
|
|
31
36
|
import type { AbideFile } from './parseAbide.ts'
|
|
32
37
|
import {
|
|
33
38
|
CLIENT_LANE,
|
|
@@ -551,6 +556,88 @@ function emitRegion(sink: Cursor, region: RegionEmission): RegionEmission {
|
|
|
551
556
|
live.add(at)
|
|
552
557
|
}
|
|
553
558
|
|
|
559
|
+
for (const line of walkTo(positions, live, lowering)) chain.push(line)
|
|
560
|
+
|
|
561
|
+
// THE UNITS — text, listeners, attributes, the regions, then the node handed over. The walk
|
|
562
|
+
// does not depend on the order.
|
|
563
|
+
// 32.68 — A `bind:element` CALLBACK RUNS LAST, so the node it is handed is whole: a child
|
|
564
|
+
// component, an arm or a list inside it has built, and `form.querySelector('input')` finds an
|
|
565
|
+
// `<input>` an `<Input>` makes.
|
|
566
|
+
// 32.67 — A ROW'S ITEM WATCHES FIRST, one after another, which is how its scope finds them
|
|
567
|
+
// (`Scope.itemWatch`).
|
|
568
|
+
const items: string[] = []
|
|
569
|
+
const texts: string[] = []
|
|
570
|
+
const handlers: string[] = []
|
|
571
|
+
const attributes: string[] = []
|
|
572
|
+
const elements: string[] = []
|
|
573
|
+
const regions: PlacedNode[] = []
|
|
574
|
+
for (const at of positions) {
|
|
575
|
+
if (!live.has(at)) continue
|
|
576
|
+
if (at.kind === 'region') {
|
|
577
|
+
regions.push(at)
|
|
578
|
+
continue
|
|
579
|
+
}
|
|
580
|
+
const slot = at.name.slice('__c'.length)
|
|
581
|
+
if (at.kind === 'text') {
|
|
582
|
+
if (readsOwnItem(at.run, lowering))
|
|
583
|
+
items.push(textUnit(at, slot, lowering, 'itemWatch'))
|
|
584
|
+
else texts.push(textUnit(at, slot, lowering, 'watch'))
|
|
585
|
+
continue
|
|
586
|
+
}
|
|
587
|
+
const units = elementUnits(
|
|
588
|
+
`${lowering.use('elementHere')}(${at.name}, ${JSON.stringify(at.tag)})`,
|
|
589
|
+
bound.get(at) as ClassifiedAttribute[],
|
|
590
|
+
slot,
|
|
591
|
+
lowering,
|
|
592
|
+
)
|
|
593
|
+
for (const unit of units.items) items.push(unit)
|
|
594
|
+
for (const unit of units.handlers) handlers.push(unit)
|
|
595
|
+
for (const unit of units.attributes) attributes.push(unit)
|
|
596
|
+
for (const unit of units.elements) elements.push(unit)
|
|
597
|
+
}
|
|
598
|
+
for (const unit of items) lines.push(unit)
|
|
599
|
+
for (const unit of texts) lines.push(unit)
|
|
600
|
+
for (const unit of handlers) lines.push(unit)
|
|
601
|
+
for (const unit of attributes) lines.push(unit)
|
|
602
|
+
|
|
603
|
+
// 32.67 — WHAT A ROW'S REGIONS AND COMPONENTS BIND reads the row's item through its cell: the
|
|
604
|
+
// row's `update` reaches its own top level and no further.
|
|
605
|
+
lowering.nestItems(() => {
|
|
606
|
+
for (const at of regions) {
|
|
607
|
+
const holder = at.holder
|
|
608
|
+
if (holder.kind === 'block') {
|
|
609
|
+
if (holder.block === 'for') emitList(at, holder, region)
|
|
610
|
+
else if (holder.block === 'try') emitTry(at, holder, region)
|
|
611
|
+
else emitBlock(at, holder, region)
|
|
612
|
+
} else if (holder.kind === 'element') {
|
|
613
|
+
if (isSlot(holder)) emitSlot(at, region)
|
|
614
|
+
else emitInvocation(at, holder, region)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
for (const unit of elements) lines.push(unit)
|
|
618
|
+
// 32.43 — A REGION THAT MOUNTS NOTHING INVOKES NOTHING, so what it declares is reached
|
|
619
|
+
// from nowhere and is not declared: a row declaring a component it never invokes stays a
|
|
620
|
+
// row whose mount is the shared empty one.
|
|
621
|
+
if (lines.length > 0) region.defs = definitions(sink, region)
|
|
622
|
+
})
|
|
623
|
+
return region
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* The walk to every position in `live`: one `const` per position it needs, each found from the one
|
|
628
|
+
* before it — the region's `chain` (`emitRegion`), and a row's `update` (32.67).
|
|
629
|
+
*
|
|
630
|
+
* THE WALK IS PRUNED. A position is walked when something at or after it in its parent needs it —
|
|
631
|
+
* a binding, or a later position found through it — so a static subtree and a static tail are
|
|
632
|
+
* never visited. A blank run standing before something walked is put back as the walk passes
|
|
633
|
+
* (`runHere`), because what follows it is found from it.
|
|
634
|
+
*/
|
|
635
|
+
function walkTo(
|
|
636
|
+
positions: readonly PlacedNode[],
|
|
637
|
+
live: ReadonlySet<PlacedNode>,
|
|
638
|
+
lowering: Lowering,
|
|
639
|
+
): string[] {
|
|
640
|
+
const chain: string[] = []
|
|
554
641
|
// BACKWARDS, so everything after a position — its later siblings and its own descendants — is
|
|
555
642
|
// decided before it is. `followed` holds the frames, keyed by the element they stand inside,
|
|
556
643
|
// that have a walked position later than the one being decided.
|
|
@@ -583,92 +670,64 @@ function emitRegion(sink: Cursor, region: RegionEmission): RegionEmission {
|
|
|
583
670
|
else chain.push(`const ${at.name} = ${at.from}`)
|
|
584
671
|
}
|
|
585
672
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
for (const
|
|
595
|
-
if (
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
//
|
|
626
|
-
//
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
// node held. 32.26 is the one write that changes it: a `Raw` part turns the run into an
|
|
630
|
-
// anchored one (`setMarkup`) and a later text value turns it back, so each write hands
|
|
631
|
-
// back what the run now is and the binding holds that.
|
|
632
|
-
texts.push(
|
|
633
|
-
`let __t${slot} = ${lowering.use('textHere')}(${at.name})
|
|
634
|
-
${SCOPE}.watch(() => {
|
|
635
|
-
const ${values.join(', ')}
|
|
636
|
-
__t${slot} = ${brands.join(' || ')} ? ${lowering.use('setMarkup')}(__t${slot}, ${markup.join(' + ')}) : ${lowering.use('setText')}(__t${slot}, ${text.join(' + ')})
|
|
637
|
-
})`,
|
|
673
|
+
return chain
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/** 32.67 — whether a text run reads a row's item at that row's own top level (`readsOwnItem`). */
|
|
677
|
+
function readsOwnItem(
|
|
678
|
+
run: readonly TemplateNode[],
|
|
679
|
+
lowering: Lowering,
|
|
680
|
+
): boolean {
|
|
681
|
+
for (const piece of run)
|
|
682
|
+
if (piece.kind === 'expression' && lowering.readsOwnItem(piece))
|
|
683
|
+
return true
|
|
684
|
+
return false
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* One text run's write: the run recomputed whole, as markup where a part is `Raw` (32.26) and as
|
|
689
|
+
* text otherwise — kept written by a watch where `watched`, and written once for a row's `update`.
|
|
690
|
+
*
|
|
691
|
+
* RESOLVED ONCE, and the binding closes over what it resolved to. The node a position lands on is
|
|
692
|
+
* the same one on every run while the run holds text — the walk put a blank run's back and
|
|
693
|
+
* `setText` never removes it — so re-finding it per write is the same answer bought again: measured
|
|
694
|
+
* in Chromium at 0.47 µs a write against 0.22 µs with the node held. 32.26 is the one write that
|
|
695
|
+
* changes it: a `Raw` part turns the run into an anchored one (`setMarkup`) and a later text value
|
|
696
|
+
* turns it back, so each write hands back what the run now is and the binding holds that.
|
|
697
|
+
*/
|
|
698
|
+
function textUnit(
|
|
699
|
+
at: PlacedNode,
|
|
700
|
+
slot: string,
|
|
701
|
+
lowering: Lowering,
|
|
702
|
+
watched: 'watch' | 'itemWatch' | null,
|
|
703
|
+
): string {
|
|
704
|
+
const values: string[] = []
|
|
705
|
+
const brands: string[] = []
|
|
706
|
+
const text: string[] = []
|
|
707
|
+
const markup: string[] = []
|
|
708
|
+
for (let piece = 0; piece < at.run.length; piece += 1) {
|
|
709
|
+
const node = at.run[piece] as TemplateNode
|
|
710
|
+
if (node.kind === 'text') {
|
|
711
|
+
markup.push(JSON.stringify(node.text))
|
|
712
|
+
// 32.27 — static text is markup, and `data` is not a parse: a piece with an entity in
|
|
713
|
+
// it goes in as the characters it stands for, decoded by the build.
|
|
714
|
+
text.push(
|
|
715
|
+
JSON.stringify(decodeCharacterReferences(node.text, 'text')),
|
|
638
716
|
)
|
|
639
717
|
continue
|
|
640
718
|
}
|
|
641
|
-
const
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
)
|
|
647
|
-
for (const unit of units.handlers) handlers.push(unit)
|
|
648
|
-
for (const unit of units.attributes) attributes.push(unit)
|
|
649
|
-
for (const unit of units.elements) elements.push(unit)
|
|
650
|
-
}
|
|
651
|
-
for (const unit of texts) lines.push(unit)
|
|
652
|
-
for (const unit of handlers) lines.push(unit)
|
|
653
|
-
for (const unit of attributes) lines.push(unit)
|
|
654
|
-
for (const unit of elements) lines.push(unit)
|
|
655
|
-
|
|
656
|
-
for (const at of regions) {
|
|
657
|
-
const holder = at.holder
|
|
658
|
-
if (holder.kind === 'block') {
|
|
659
|
-
if (holder.block === 'for') emitList(at, holder, region)
|
|
660
|
-
else if (holder.block === 'try') emitTry(at, holder, region)
|
|
661
|
-
else emitBlock(at, holder, region)
|
|
662
|
-
} else if (holder.kind === 'element') {
|
|
663
|
-
if (isSlot(holder)) emitSlot(at, region)
|
|
664
|
-
else emitInvocation(at, holder, region)
|
|
665
|
-
}
|
|
719
|
+
const value = `__v${slot}_${piece}`
|
|
720
|
+
values.push(`${value} = ${lowering.binding(node)}`)
|
|
721
|
+
brands.push(`${lowering.use('isRaw')}(${value})`)
|
|
722
|
+
text.push(`${lowering.use('escapeless')}(${value})`)
|
|
723
|
+
markup.push(`${lowering.use('escapeText')}(${value})`)
|
|
666
724
|
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
725
|
+
const write = ` const ${values.join(', ')}
|
|
726
|
+
__t${slot} = ${brands.join(' || ')} ? ${lowering.use('setMarkup')}(__t${slot}, ${markup.join(' + ')}) : ${lowering.use('setText')}(__t${slot}, ${text.join(' + ')})`
|
|
727
|
+
const found = `let __t${slot} = ${lowering.use('textHere')}(${at.name})`
|
|
728
|
+
return watched === null
|
|
729
|
+
? `{\n${found}\n${write}\n}`
|
|
730
|
+
: `${found}\n${SCOPE}.${watched}(() => {\n${write}\n})`
|
|
672
731
|
}
|
|
673
732
|
|
|
674
733
|
/**
|
|
@@ -686,18 +745,25 @@ function elementUnits(
|
|
|
686
745
|
classified: ClassifiedAttribute[],
|
|
687
746
|
slot: string,
|
|
688
747
|
lowering: Lowering,
|
|
689
|
-
): {
|
|
748
|
+
): {
|
|
749
|
+
items: string[]
|
|
750
|
+
handlers: string[]
|
|
751
|
+
attributes: string[]
|
|
752
|
+
elements: string[]
|
|
753
|
+
} {
|
|
754
|
+
const items: string[] = []
|
|
690
755
|
const handlers: string[] = []
|
|
691
756
|
const attributes: string[] = []
|
|
692
757
|
const elements: string[] = []
|
|
693
758
|
const writes: string[] = []
|
|
759
|
+
const itemWrites: string[] = []
|
|
694
760
|
const binds: string[] = []
|
|
695
761
|
const spreads: string[] = []
|
|
696
762
|
for (const one of classified) {
|
|
697
763
|
if (!isBound(one)) continue
|
|
698
764
|
if (one.kind === 'handler')
|
|
699
765
|
handlers.push(
|
|
700
|
-
`${element}.addEventListener(${JSON.stringify(one.event)}, ${lowering.
|
|
766
|
+
`${element}.addEventListener(${JSON.stringify(one.event)}, ${lowering.listener(one.attribute)})`,
|
|
701
767
|
)
|
|
702
768
|
// 32.5, 32.7, 32.8 — THE PROPERTY AND THE VALUE, IN STEP BOTH WAYS: a watch on this
|
|
703
769
|
// region's scope writes the property, and a listener writes it back.
|
|
@@ -732,16 +798,25 @@ if (__r${slot} !== undefined) ${SCOPE}.stops.push(__r${slot})`,
|
|
|
732
798
|
write: 'attribute' as const,
|
|
733
799
|
name: one.attribute.name,
|
|
734
800
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
)
|
|
801
|
+
// 32.67 — one reading a row's item is one of the row's item watches, on a name of its
|
|
802
|
+
// own because it is installed apart from the element's other attributes.
|
|
803
|
+
if (lowering.readsOwnItem(one.attribute))
|
|
804
|
+
itemWrites.push(
|
|
805
|
+
`${SCOPE}.itemWatch(() => ${lowering.use(WRITES[write])}(__i${slot}, ${JSON.stringify(name)}, ${lowering.binding(one.attribute)}))`,
|
|
806
|
+
)
|
|
807
|
+
else
|
|
808
|
+
writes.push(
|
|
809
|
+
`${SCOPE}.watch(() => ${lowering.use(WRITES[write])}(__a${slot}, ${JSON.stringify(name)}, ${lowering.binding(one.attribute)}))`,
|
|
810
|
+
)
|
|
738
811
|
}
|
|
739
812
|
}
|
|
813
|
+
if (itemWrites.length > 0)
|
|
814
|
+
items.push(`const __i${slot} = ${element}\n${itemWrites.join('\n')}`)
|
|
740
815
|
if (writes.length > 0)
|
|
741
816
|
attributes.push(`const __a${slot} = ${element}\n${writes.join('\n')}`)
|
|
742
817
|
for (const unit of binds) attributes.push(unit)
|
|
743
818
|
for (const unit of spreads) attributes.push(unit)
|
|
744
|
-
return { handlers, attributes, elements }
|
|
819
|
+
return { items, handlers, attributes, elements }
|
|
745
820
|
}
|
|
746
821
|
|
|
747
822
|
// A region's bindings go through its SCOPE, so what replaced the region can stop them (see
|
|
@@ -1331,11 +1406,38 @@ function emitList(
|
|
|
1331
1406
|
: written
|
|
1332
1407
|
|
|
1333
1408
|
const single = isSingleRooted(block.children)
|
|
1409
|
+
// 32.67 — the row's RECORD, under a name of the list's own, for a listener reading the item off
|
|
1410
|
+
// it: a row nested in this one takes `ROW_ROOT` for its own record.
|
|
1411
|
+
const record = `${name}_row`
|
|
1334
1412
|
|
|
1335
|
-
|
|
1413
|
+
let rowRegion = region.nested()
|
|
1414
|
+
const mounted = lowering.readItemsAs(block, 'own', record, () => {
|
|
1415
|
+
rowRegion = emitRegion(row, region.nested())
|
|
1416
|
+
})
|
|
1417
|
+
let local = ''
|
|
1418
|
+
const setUp =
|
|
1419
|
+
block.setup === null
|
|
1420
|
+
? { cell: 0, record: 0 }
|
|
1421
|
+
: lowering.readItemsAs(block, 'nested', record, () => {
|
|
1422
|
+
local = lowering.setup(block.setup as Setup)
|
|
1423
|
+
})
|
|
1336
1424
|
|
|
1337
|
-
//
|
|
1338
|
-
|
|
1425
|
+
// MADE FROM THE ITEM'S VALUE: every slot reading the item alone is written before the row is
|
|
1426
|
+
// inserted, so making a row writes the document once per node (32.67).
|
|
1427
|
+
const valued = `const ${item} = ${ROW_ROOT}.data`
|
|
1428
|
+
const make = `const ${name}_make = (__document, ${ROW_ROOT}, ${at}) => {\n ${valued}\n ${emitMaker(row, lowering, () => rowMarkup(row, !single, lowering, CLIENT_LANE), !single)}\n}`
|
|
1429
|
+
const walk = rowRegion.body()
|
|
1430
|
+
// A PATTERN READ OFF THE RECORD is read through one picker a name, the list's rather than the
|
|
1431
|
+
// row's, so a row allocates nothing for it.
|
|
1432
|
+
if (mounted.record > 0 && !isNamedAlone(item))
|
|
1433
|
+
for (const read of lowering.itemNames(block))
|
|
1434
|
+
region.helpers.push(
|
|
1435
|
+
`const ${record}_${read} = (__value) => { const ${item} = __value; return ${read} }`,
|
|
1436
|
+
)
|
|
1437
|
+
const opened =
|
|
1438
|
+
walk.length === 0
|
|
1439
|
+
? walk
|
|
1440
|
+
: [...itemBinding(block, item, mounted, record, lowering), ...walk]
|
|
1339
1441
|
|
|
1340
1442
|
// A row that cannot be hydrated is REBUILT by the same `make`, in place, and the row's record
|
|
1341
1443
|
// takes its new first node.
|
|
@@ -1343,16 +1445,17 @@ function emitList(
|
|
|
1343
1445
|
make,
|
|
1344
1446
|
mountFunction(
|
|
1345
1447
|
name,
|
|
1346
|
-
`${ROW_ROOT}, ${
|
|
1448
|
+
`${ROW_ROOT}, ${at}`,
|
|
1347
1449
|
// The row's walk, INSIDE the mount's one `try` rather than above it, because a row
|
|
1348
1450
|
// rebuilds whole: a node that refused is the row disagreeing, and re-making it is the
|
|
1349
1451
|
// answer the page does not have.
|
|
1350
|
-
|
|
1351
|
-
`${lowering.use('replaceRow')}(${ROW_ROOT}, ${name}_make(${ROW_ROOT}.node.ownerDocument, ${
|
|
1452
|
+
opened,
|
|
1453
|
+
`${lowering.use('replaceRow')}(${ROW_ROOT}, ${name}_make(${ROW_ROOT}.node.ownerDocument, ${ROW_ROOT}, ${at}))`,
|
|
1352
1454
|
lowering,
|
|
1353
1455
|
row.defersSetup,
|
|
1354
1456
|
block.setup !== null,
|
|
1355
1457
|
),
|
|
1458
|
+
rowUpdate(name, row, block, valued, at, lowering),
|
|
1356
1459
|
].join('\n')
|
|
1357
1460
|
// THE LIST CALLS `_setUp` ONCE PER ROW (`List.setUp`). 14.8 — a row with a branch-local
|
|
1358
1461
|
// `<script>` runs it there, and the row's `make` and `mount` are declared after it, so both close
|
|
@@ -1365,13 +1468,17 @@ function emitList(
|
|
|
1365
1468
|
block.setup !== null
|
|
1366
1469
|
? rowSetUp(
|
|
1367
1470
|
name,
|
|
1368
|
-
|
|
1471
|
+
[
|
|
1472
|
+
...itemBinding(block, item, setUp, record, lowering),
|
|
1473
|
+
// A setup reading nothing of the item still sees its value.
|
|
1474
|
+
...(setUp.cell === 0 ? [valued] : []),
|
|
1475
|
+
].join('\n'),
|
|
1369
1476
|
at,
|
|
1370
|
-
|
|
1477
|
+
local,
|
|
1371
1478
|
builders,
|
|
1372
1479
|
lowering,
|
|
1373
1480
|
)
|
|
1374
|
-
: `${builders}\nconst ${name}_builders = { make: ${name}_make, mount: ${name}_mount }\nconst ${name}_setUp = () => ${name}_builders`,
|
|
1481
|
+
: `${builders}\nconst ${name}_builders = { make: ${name}_make, mount: ${name}_mount, update: ${name}_update }\nconst ${name}_setUp = () => ${name}_builders`,
|
|
1375
1482
|
].join('\n'),
|
|
1376
1483
|
)
|
|
1377
1484
|
|
|
@@ -1386,6 +1493,101 @@ ${SCOPE}.watch(() => ${lowering.use('updateList')}(${name}_list, ${name}_subject
|
|
|
1386
1493
|
)
|
|
1387
1494
|
}
|
|
1388
1495
|
|
|
1496
|
+
/**
|
|
1497
|
+
* Binds a row function's item names for how `uses` says they were read (32.67): its record under
|
|
1498
|
+
* `record` for a listener, and the row's cell for everything else — the name itself where the item
|
|
1499
|
+
* is one name, and an accessor per name destructuring the cell's value where it is a pattern.
|
|
1500
|
+
*/
|
|
1501
|
+
function itemBinding(
|
|
1502
|
+
block: object,
|
|
1503
|
+
item: string,
|
|
1504
|
+
uses: ItemUses,
|
|
1505
|
+
record: string,
|
|
1506
|
+
lowering: Lowering,
|
|
1507
|
+
): string[] {
|
|
1508
|
+
const lines: string[] = []
|
|
1509
|
+
if (uses.record > 0) lines.push(`const ${record} = ${ROW_ROOT}`)
|
|
1510
|
+
if (uses.cell === 0) return lines
|
|
1511
|
+
const cell = `${lowering.use('rowCell')}(${ROW_ROOT})`
|
|
1512
|
+
if (isNamedAlone(item)) {
|
|
1513
|
+
lines.push(`const ${item} = ${cell}`)
|
|
1514
|
+
return lines
|
|
1515
|
+
}
|
|
1516
|
+
lines.push(`const __cell = ${cell}`)
|
|
1517
|
+
for (const name of lowering.itemNames(block))
|
|
1518
|
+
lines.push(
|
|
1519
|
+
`const ${name} = () => { const ${item} = __cell(); return ${name} }`,
|
|
1520
|
+
)
|
|
1521
|
+
return lines
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
/**
|
|
1525
|
+
* A row's `update` (32.67): the walk to each text and attribute slot of the row's own top level that
|
|
1526
|
+
* reads the item and nothing that moves, and one write of each from the record's value.
|
|
1527
|
+
*
|
|
1528
|
+
* WHAT `make` WROTE FROM THE ITEM, written again — and nothing a binding keeps written. A bound
|
|
1529
|
+
* slot is its watch's alone (`Scope.itemWatch`), rerun when the row takes a new item (`updateRow`):
|
|
1530
|
+
* a run's node can change kind under a write (32.26), and what the slot reads can change with the
|
|
1531
|
+
* item, so the node and the subscriptions are each held by one writer. A region of the row binds
|
|
1532
|
+
* its own. A row with no such slot shares one `update` that does nothing.
|
|
1533
|
+
*/
|
|
1534
|
+
function rowUpdate(
|
|
1535
|
+
name: string,
|
|
1536
|
+
row: Cursor,
|
|
1537
|
+
block: object,
|
|
1538
|
+
valued: string,
|
|
1539
|
+
at: string,
|
|
1540
|
+
lowering: Lowering,
|
|
1541
|
+
): string {
|
|
1542
|
+
const live = new Set<PlacedNode>()
|
|
1543
|
+
const writes: string[] = []
|
|
1544
|
+
for (const position of row.positions) {
|
|
1545
|
+
if (position.kind === 'region') continue
|
|
1546
|
+
const slot = position.name.slice('__c'.length)
|
|
1547
|
+
if (position.kind === 'text') {
|
|
1548
|
+
let reads = false
|
|
1549
|
+
for (const piece of position.run)
|
|
1550
|
+
if (piece.kind === 'expression')
|
|
1551
|
+
reads ||= lowering.readsItem(piece, block)
|
|
1552
|
+
if (!reads || lowering.isLiveRun(position.run)) continue
|
|
1553
|
+
live.add(position)
|
|
1554
|
+
writes.push(textUnit(position, slot, lowering, null))
|
|
1555
|
+
continue
|
|
1556
|
+
}
|
|
1557
|
+
const element = `${lowering.use('elementHere')}(${position.name}, ${JSON.stringify(position.tag)})`
|
|
1558
|
+
const own: string[] = []
|
|
1559
|
+
for (const one of classifyAttributes(
|
|
1560
|
+
position.holder as ElementNode,
|
|
1561
|
+
lowering,
|
|
1562
|
+
)) {
|
|
1563
|
+
if (!one.attribute.expression || one.live) continue
|
|
1564
|
+
if (!lowering.readsItem(one.attribute, block)) continue
|
|
1565
|
+
const toggles =
|
|
1566
|
+
one.kind === 'merged'
|
|
1567
|
+
? one.toggles
|
|
1568
|
+
: one.kind === 'dynamic'
|
|
1569
|
+
? {
|
|
1570
|
+
write: 'attribute' as const,
|
|
1571
|
+
name: one.attribute.name,
|
|
1572
|
+
}
|
|
1573
|
+
: undefined
|
|
1574
|
+
if (toggles === undefined) continue
|
|
1575
|
+
own.push(
|
|
1576
|
+
`${lowering.use(WRITES[toggles.write])}(__a${slot}, ${JSON.stringify(toggles.name)}, ${lowering.expression(one.attribute)})`,
|
|
1577
|
+
)
|
|
1578
|
+
}
|
|
1579
|
+
if (own.length === 0) continue
|
|
1580
|
+
live.add(position)
|
|
1581
|
+
writes.push(`{\nconst __a${slot} = ${element}\n${own.join('\n')}\n}`)
|
|
1582
|
+
}
|
|
1583
|
+
if (writes.length === 0)
|
|
1584
|
+
return `const ${name}_update = ${lowering.use('noUpdate')}`
|
|
1585
|
+
return `const ${name}_update = (${ROW_ROOT}, ${at}) => {
|
|
1586
|
+
${valued}
|
|
1587
|
+
${[...walkTo(row.positions, live, lowering), ...writes].join('\n')}
|
|
1588
|
+
}`
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1389
1591
|
/**
|
|
1390
1592
|
* A row's `_setUp` where the row has a branch-local `<script>` (14.8): runs it once for the row,
|
|
1391
1593
|
* and answers the builders that close over its names.
|
|
@@ -1394,18 +1596,19 @@ ${SCOPE}.watch(() => ${lowering.use('updateList')}(${name}_list, ${name}_subject
|
|
|
1394
1596
|
*/
|
|
1395
1597
|
function rowSetUp(
|
|
1396
1598
|
name: string,
|
|
1397
|
-
|
|
1599
|
+
bound: string,
|
|
1398
1600
|
at: string,
|
|
1399
1601
|
local: string,
|
|
1400
1602
|
builders: string,
|
|
1401
1603
|
lowering: Lowering,
|
|
1402
1604
|
): string {
|
|
1403
1605
|
const owning = scopedSetup(local, lowering, true)
|
|
1404
|
-
// The mount takes the setUp's own
|
|
1405
|
-
// the
|
|
1406
|
-
return `const ${name}_setUp = (${
|
|
1606
|
+
// The mount takes the setUp's own record and index again, so the row record `List` hands in is
|
|
1607
|
+
// the only thing that is new.
|
|
1608
|
+
return `const ${name}_setUp = (${ROW_ROOT}, ${at}) => {
|
|
1609
|
+
${bound}
|
|
1407
1610
|
${owning.opened}${local}
|
|
1408
1611
|
${owning.handed}${builders}
|
|
1409
|
-
return { make: ${name}_make, mount: ${name}_mount }${owning.closed}
|
|
1612
|
+
return { make: ${name}_make, mount: ${name}_mount, update: ${name}_update }${owning.closed}
|
|
1410
1613
|
}`
|
|
1411
1614
|
}
|
|
@@ -58,6 +58,8 @@ export async function handlersUnder(
|
|
|
58
58
|
paths.push(file.path)
|
|
59
59
|
named.push(file.relative)
|
|
60
60
|
}
|
|
61
|
+
// The project is asked of below, so the session keeps its snapshot until then.
|
|
62
|
+
using _held = session.hold()
|
|
61
63
|
const project = await session.projectWith(paths)
|
|
62
64
|
for (const source of serving) {
|
|
63
65
|
paths.push(session.projectionOf(source.path))
|
|
@@ -125,6 +127,7 @@ export async function handlersUnder(
|
|
|
125
127
|
copy: handler.copy,
|
|
126
128
|
defaults: handler.defaults,
|
|
127
129
|
args: handler.args,
|
|
130
|
+
result: handler.result,
|
|
128
131
|
})
|
|
129
132
|
warnings.push(...handler.unread)
|
|
130
133
|
} catch (thrown) {
|
|
@@ -155,6 +158,7 @@ export async function handlersUnder(
|
|
|
155
158
|
copy: each.copy,
|
|
156
159
|
defaults: each.defaults,
|
|
157
160
|
args: each.args,
|
|
161
|
+
result: each.result,
|
|
158
162
|
})
|
|
159
163
|
warnings.push(...each.unread)
|
|
160
164
|
}
|
|
@@ -31,7 +31,7 @@ export function invocationProps(
|
|
|
31
31
|
const key = propKeyOf(attribute)
|
|
32
32
|
if (key === undefined)
|
|
33
33
|
throw new Error(
|
|
34
|
-
`abide: ${attribute.name} on <${node.tag}> is not a prop a component takes — \`bind:\` hands over the \`Reactive\` in its braces (32.6),
|
|
34
|
+
`abide: ${attribute.name} on <${node.tag}> is not a prop a component takes — \`bind:\` hands over the \`Reactive\` in its braces (RULEBOOK 32.6), \`class:\` and \`style:\` set an element's own class and style (RULEBOOK 32.10), and \`bind:element\` hands over an element — put it on one inside <${node.tag}>, or pass the callback as a prop (RULEBOOK 32.69).`,
|
|
35
35
|
)
|
|
36
36
|
// A STATIC PROP IS STILL AN ACCESSOR. One shape per prop whatever the call site spelled
|
|
37
37
|
// (33.10), so the child reads every key the same way and a literal is not a second case.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { citedClauses } from './citedClauses.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 14.19 — whether the line above `offset` in `text` silences the warning `message`: it holds
|
|
5
|
+
* `abide-ignore` followed by the number of a clause the message cites, in a script comment or a
|
|
6
|
+
* markup one alike. A directive naming another clause silences nothing, so one silence never hides
|
|
7
|
+
* a second warning on the same read.
|
|
8
|
+
*/
|
|
9
|
+
export function isIgnored(
|
|
10
|
+
text: string,
|
|
11
|
+
offset: number,
|
|
12
|
+
message: string,
|
|
13
|
+
): boolean {
|
|
14
|
+
const cited = citedClauses(message)
|
|
15
|
+
if (cited.length === 0) return false
|
|
16
|
+
const lineStart = text.lastIndexOf('\n', offset - 1)
|
|
17
|
+
if (lineStart <= 0) return false
|
|
18
|
+
const above = text.slice(
|
|
19
|
+
text.lastIndexOf('\n', lineStart - 1) + 1,
|
|
20
|
+
lineStart,
|
|
21
|
+
)
|
|
22
|
+
const directive = /abide-ignore((?:[ \t]+\d+\.\d+)+)/.exec(above)
|
|
23
|
+
if (directive === null) return false
|
|
24
|
+
for (const clause of (directive[1] as string).trim().split(/\s+/))
|
|
25
|
+
if (cited.includes(clause)) return true
|
|
26
|
+
return false
|
|
27
|
+
}
|