@abide/abide 0.117.2 → 0.119.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.
Files changed (156) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/abide.ts +74 -11
  3. package/docs/REGISTRY.md +18 -15
  4. package/docs/RULEBOOK.md +71 -10
  5. package/docs/abide.md +584 -274
  6. package/package.json +1 -1
  7. package/scaffold/AGENTS.md +1 -1
  8. package/scaffold/src/getGreeting.test.ts +2 -1
  9. package/scaffold/src/index.abide +5 -2
  10. package/scaffold/src/index.test.ts +2 -0
  11. package/scaffold/src/layout.abide +6 -6
  12. package/src/compiler/Lowering.ts +20 -2
  13. package/src/compiler/PositionTable.ts +1 -0
  14. package/src/compiler/Refusals.ts +1 -0
  15. package/src/compiler/TypeSession.ts +49 -13
  16. package/src/compiler/aliasedSymbol.ts +1 -1
  17. package/src/compiler/ambientReadsIn.ts +1 -0
  18. package/src/compiler/appLayout.ts +55 -3
  19. package/src/compiler/appMap.ts +15 -0
  20. package/src/compiler/argsDefaultsIn.ts +1 -0
  21. package/src/compiler/argsSchemaOf.ts +1 -0
  22. package/src/compiler/checkApp.ts +3 -1
  23. package/src/compiler/checkedShadow.ts +2 -1
  24. package/src/compiler/classifyRead.ts +1 -0
  25. package/src/compiler/classifyTemplate.ts +1 -0
  26. package/src/compiler/compileApp.ts +5 -3
  27. package/src/compiler/compiledEntry.ts +2 -1
  28. package/src/compiler/componentGraph.ts +1 -0
  29. package/src/compiler/declaredExports.ts +4 -2
  30. package/src/compiler/emitClient.ts +19 -14
  31. package/src/compiler/emitComponent.ts +1 -0
  32. package/src/compiler/emitSource.ts +2 -1
  33. package/src/compiler/formatAbide.ts +857 -0
  34. package/src/compiler/formatDiagnosis.ts +1 -0
  35. package/src/compiler/handlersUnder.ts +7 -3
  36. package/src/compiler/hookCalls.ts +1 -0
  37. package/src/compiler/lineOf.ts +17 -0
  38. package/src/compiler/matchersOf.ts +157 -0
  39. package/src/compiler/mountsOf.ts +5 -0
  40. package/src/compiler/paramFile.ts +125 -0
  41. package/src/compiler/parseAbide.ts +48 -28
  42. package/src/compiler/projectShadow.ts +11 -4
  43. package/src/compiler/reactiveOptions.ts +1 -0
  44. package/src/compiler/reactiveTypes.ts +2 -1
  45. package/src/compiler/readsIn.ts +3 -2
  46. package/src/compiler/renderMarkup.ts +3 -2
  47. package/src/compiler/resolverIn.ts +2 -0
  48. package/src/compiler/sameTemplate.ts +150 -0
  49. package/src/compiler/scaffold.ts +4 -11
  50. package/src/compiler/serveLanguage.ts +48 -21
  51. package/src/compiler/sharedModuleWrites.ts +2 -1
  52. package/src/compiler/sourceMapOf.ts +1 -0
  53. package/src/compiler/splitImports.ts +2 -0
  54. package/src/compiler/templateWarnings.ts +2 -2
  55. package/src/compiler/trackingWarnings.ts +1 -0
  56. package/src/compiler/writeDefaultProject.ts +5 -1
  57. package/src/server/BRIDGE_VERSION.ts +1 -1
  58. package/src/server/abideModules.ts +2 -0
  59. package/src/server/answerFailure.ts +1 -0
  60. package/src/server/answerRequest.ts +57 -19
  61. package/src/server/answerRpc.ts +6 -2
  62. package/src/server/answerSocket.ts +2 -1
  63. package/src/server/answerValue.ts +1 -0
  64. package/src/server/answerWorker.ts +1 -0
  65. package/src/server/appTree.ts +18 -9
  66. package/src/server/argsFor.ts +1 -0
  67. package/src/server/assistant.ts +2 -1
  68. package/src/server/banner.ts +2 -0
  69. package/src/server/bridgeRelay.ts +1 -0
  70. package/src/server/callerOf.ts +1 -0
  71. package/src/server/claudeApi.ts +1 -0
  72. package/src/server/claudeCode.ts +1 -0
  73. package/src/server/clientBundle.ts +1 -0
  74. package/src/server/coerceArgs.ts +1 -2
  75. package/src/server/commands.ts +9 -0
  76. package/src/server/compressAnswer.ts +2 -1
  77. package/src/server/config.ts +2 -0
  78. package/src/server/createApp.ts +4 -2
  79. package/src/server/crossingOrigin.ts +1 -0
  80. package/src/server/csp.ts +1 -0
  81. package/src/server/deferredBuilds.ts +1 -0
  82. package/src/server/derivedArgs.ts +1 -0
  83. package/src/server/devWorker.ts +2 -1
  84. package/src/server/faceOf.ts +2 -7
  85. package/src/server/generatedSurfaces.ts +79 -7
  86. package/src/server/hooks.ts +1 -0
  87. package/src/server/iconAccent.ts +1 -1
  88. package/src/server/installServer.ts +1 -0
  89. package/src/server/installSourceMaps.ts +1 -0
  90. package/src/server/launcherPage.ts +2 -2
  91. package/src/server/manifestOf.ts +1 -0
  92. package/src/server/muxUpgrade.ts +1 -0
  93. package/src/server/nativeMenu.ts +2 -0
  94. package/src/server/placement.ts +12 -2
  95. package/src/server/principal.ts +2 -1
  96. package/src/server/remote.ts +7 -2
  97. package/src/server/render.ts +3 -3
  98. package/src/server/renderComponent.ts +1 -0
  99. package/src/server/reportHealth.ts +1 -0
  100. package/src/server/requestScope.ts +3 -1
  101. package/src/server/runDesktop.ts +3 -2
  102. package/src/server/seedBuffer.ts +1 -0
  103. package/src/server/serveDev.ts +3 -2
  104. package/src/server/shell.ts +2 -0
  105. package/src/server/streamEvent.ts +1 -0
  106. package/src/server/surfaceOf.ts +6 -1
  107. package/src/server/testBuild.ts +1 -0
  108. package/src/server/testMount.ts +1 -0
  109. package/src/server/trace.ts +2 -0
  110. package/src/server/withAbideSources.ts +1 -0
  111. package/src/shared/Attributes.ts +12 -8
  112. package/src/shared/cookies.ts +1 -1
  113. package/src/shared/escapeText.ts +1 -1
  114. package/src/shared/filledPath.ts +16 -10
  115. package/src/shared/index.ts +1 -5
  116. package/src/shared/jsonSchema.ts +1 -3
  117. package/src/shared/matchRoute.ts +16 -4
  118. package/src/shared/menuBridge.ts +1 -2
  119. package/src/shared/outbox.ts +1 -1
  120. package/src/shared/reactive/Keyed.ts +2 -2
  121. package/src/shared/reactive/Reactive.ts +12 -16
  122. package/src/shared/reactive/index.ts +3 -4
  123. package/src/shared/reactive/isReactive.ts +1 -1
  124. package/src/shared/reactive/scoped.ts +2 -2
  125. package/src/shared/reactive/share.ts +1 -1
  126. package/src/shared/reactive/watch.ts +4 -4
  127. package/src/shared/refuse.ts +1 -1
  128. package/src/shared/routePattern.ts +66 -14
  129. package/src/shared/rpc.ts +8 -11
  130. package/src/shared/url.ts +24 -7
  131. package/src/shared/validateJson.ts +1 -1
  132. package/src/ui/ArmRegion.ts +1 -0
  133. package/src/ui/Level.ts +1 -0
  134. package/src/ui/List.ts +1 -0
  135. package/src/ui/RawRun.ts +1 -0
  136. package/src/ui/Router.ts +1 -0
  137. package/src/ui/TryRegion.ts +1 -0
  138. package/src/ui/adoptWalk.ts +2 -1
  139. package/src/ui/bindElement.ts +1 -0
  140. package/src/ui/bindFiles.ts +1 -0
  141. package/src/ui/bindGroup.ts +1 -0
  142. package/src/ui/bindProperty.ts +1 -0
  143. package/src/ui/bindSpread.ts +1 -0
  144. package/src/ui/index.ts +1 -6
  145. package/src/ui/outboxQueue.ts +1 -1
  146. package/src/ui/parseMarkup.ts +2 -1
  147. package/src/ui/reconcile.ts +2 -1
  148. package/src/ui/reconcileHead.ts +1 -0
  149. package/src/ui/regions.ts +2 -1
  150. package/src/ui/registerWorker.ts +1 -0
  151. package/src/ui/rows.ts +1 -0
  152. package/src/ui/setMarkup.ts +1 -0
  153. package/src/ui/setText.ts +1 -0
  154. package/src/ui/socketWire.ts +4 -3
  155. package/src/ui/wire.ts +2 -1
  156. /package/scaffold/src/{getGreeting.ts → getGreeting.handler.ts} +0 -0
@@ -1,10 +1,10 @@
1
- import { REGION_CLOSE, REGION_OPEN, ROW_OPEN } from '#shared/ANCHORS.ts'
2
1
  import { anchorMarkup, regionMarkup } from '#shared/anchorMarkup.ts'
2
+ import { REGION_CLOSE, REGION_OPEN, ROW_OPEN } from '#shared/ANCHORS.ts'
3
3
  import { armAnchor } from '#shared/armAnchor.ts'
4
4
  import { CHILDREN } from '#shared/CHILDREN.ts'
5
5
  import { escapeQuote } from '#shared/escapeText.ts'
6
6
  import { headKeyOf } from '#shared/headKeyOf.ts'
7
- import { Cursor, type PlacedNode, placeTemplate } from './Cursor.ts'
7
+
8
8
  import {
9
9
  attributeOf,
10
10
  type ClassifiedAttribute,
@@ -15,6 +15,7 @@ import {
15
15
  isSlot,
16
16
  type MergeGroup,
17
17
  } from './classifyTemplate.ts'
18
+ import { Cursor, type PlacedNode, placeTemplate } from './Cursor.ts'
18
19
  import { invocationProps, passedName } from './invocationProps.ts'
19
20
  import { type Lowering, PROPS } from './Lowering.ts'
20
21
  import type { AbideFile } from './parseAbide.ts'
@@ -1,6 +1,8 @@
1
1
  import { existsSync, readFileSync } from 'node:fs' // bun exposes no sync existence check or read of its own
2
2
  import { dirname, posix } from 'node:path' // bun exposes no path helpers of its own
3
+
3
4
  import { resolvedFile } from '#server/abideModules.ts'
5
+
4
6
  import { nearest } from './nearest.ts'
5
7
 
6
8
  /**
@@ -0,0 +1,150 @@
1
+ import { SyntaxKind } from 'typescript/unstable/ast'
2
+
3
+ import type { AbideFile } from './parseAbide.ts'
4
+ import type { Body, InlineComponent, TemplateNode } from './TemplateNode.ts'
5
+ import { tokenize } from './tokenize.ts'
6
+
7
+ // 38.127 — WHAT `abide format` MAY CHANGE, and nothing else: the whitespace a browser collapses, a
8
+ // run's indentation where 33.17 and 33.18 read only its kind, and the code `bun fmt` printed, read as
9
+ // its tokens — a script block's in any order, since `sortImports` moves its imports. Everything else — a tag, an attribute's text, a block's shape, a line inside a template
10
+ // literal — is compared as the parser read it.
11
+
12
+ /** Whether `before` and `after` are one template, under what `abide format` may change. 38.127. */
13
+ export function sameTemplate(before: AbideFile, after: AbideFile): boolean {
14
+ return shapeOf(before) === shapeOf(after)
15
+ }
16
+
17
+ function shapeOf(file: AbideFile): string {
18
+ const parts: string[] = []
19
+ for (const code of file.modules) parts.push(blockShape(code))
20
+ for (const code of file.servers) parts.push(blockShape(code))
21
+ for (const code of file.scripts) parts.push(blockShape(code))
22
+ for (const style of file.styles) parts.push(styleShape(style))
23
+ parts.push(file.generics ?? '')
24
+ nodesShape(file.head, parts)
25
+ if (file.document !== undefined) {
26
+ nodesShape([file.document.html], parts)
27
+ nodesShape([file.document.body], parts)
28
+ }
29
+ nodesShape(file.template, parts)
30
+ for (const component of file.components) componentShape(component, parts)
31
+ return parts.join('\u0000')
32
+ }
33
+
34
+ function nodesShape(nodes: TemplateNode[], parts: string[]): void {
35
+ for (const node of nodes) {
36
+ if (node.kind === 'text') {
37
+ parts.push(`text ${textShape(node.text)}`)
38
+ continue
39
+ }
40
+ if (node.kind === 'expression') {
41
+ parts.push(`slot ${codeShape(node.source)}`)
42
+ continue
43
+ }
44
+ if (node.kind === 'element') {
45
+ parts.push(`<${node.tag}`)
46
+ for (const attribute of node.attributes)
47
+ parts.push(
48
+ attribute.expression
49
+ ? `${attribute.name}={${codeShape(attribute.value)}}`
50
+ : `${attribute.name}=${attribute.value}`,
51
+ )
52
+ nodesShape(node.children, parts)
53
+ for (const component of node.components)
54
+ componentShape(component, parts)
55
+ for (const component of node.passed)
56
+ componentShape(component, parts)
57
+ parts.push(`</${node.tag}>`)
58
+ continue
59
+ }
60
+ if (node.block === 'for') {
61
+ parts.push(
62
+ `{#for ${codeShape(node.item)},${codeShape(node.index)} of ${codeShape(node.subject)} by ${codeShape(node.key)}`,
63
+ )
64
+ bodyShape(node, parts)
65
+ } else {
66
+ parts.push(
67
+ node.block === 'try'
68
+ ? `{#try ${node.error}`
69
+ : `{#${node.block}`,
70
+ )
71
+ for (const branch of node.branches) {
72
+ parts.push(`{:${codeShape(branch.subject)}`)
73
+ bodyShape(branch, parts)
74
+ }
75
+ }
76
+ parts.push('{/}')
77
+ }
78
+ }
79
+
80
+ function bodyShape(body: Body, parts: string[]): void {
81
+ parts.push(
82
+ body.setup === null ? '' : blockShape(body.setup.text),
83
+ body.style === null ? '' : styleShape(body.style),
84
+ )
85
+ nodesShape(body.children, parts)
86
+ for (const component of body.components) componentShape(component, parts)
87
+ }
88
+
89
+ function componentShape(component: InlineComponent, parts: string[]): void {
90
+ parts.push(
91
+ `{#component ${component.name}(${component.pattern}: ${component.type})`,
92
+ )
93
+ bodyShape(component, parts)
94
+ parts.push('{/component}')
95
+ }
96
+
97
+ /**
98
+ * A text node as the browser renders it: within text, every whitespace run holding a newline is one
99
+ * space however it is indented. A node that is whitespace alone keeps its KIND — a newline alone, or
100
+ * one carrying indentation — since 33.17 and 33.18 drop the two apart.
101
+ */
102
+ function textShape(text: string): string {
103
+ if (text.trim() === '') return /[ \t]/.test(text) ? '\n ' : '\n'
104
+ return text.replace(/\s*\n\s*/g, '\n')
105
+ }
106
+
107
+ /** Code as its tokens, in order. */
108
+ function codeShape(code: string): string {
109
+ return tokensOf(code).join(' ')
110
+ }
111
+
112
+ /** A script block's code as its tokens in any order: `bun fmt`'s `sortImports` moves its imports. */
113
+ function blockShape(code: string): string {
114
+ return tokensOf(code).sort().join(' ')
115
+ }
116
+
117
+ /**
118
+ * Code's tokens, with what a printer adds or drops at will taken out: the separators `;` and `,`,
119
+ * the parentheses, and how a string or a number is spelled. A template literal's text is a token,
120
+ * so a line moved inside one is a different token.
121
+ */
122
+ function tokensOf(code: string): string[] {
123
+ const tokens: string[] = []
124
+ for (const token of tokenize(code))
125
+ switch (token.kind) {
126
+ case SyntaxKind.SemicolonToken:
127
+ case SyntaxKind.CommaToken:
128
+ case SyntaxKind.OpenParenToken:
129
+ case SyntaxKind.CloseParenToken:
130
+ continue
131
+ case SyntaxKind.StringLiteral:
132
+ tokens.push(
133
+ JSON.stringify(
134
+ token.text.slice(1, -1).replace(/\\(['"])/g, '$1'),
135
+ ),
136
+ )
137
+ continue
138
+ case SyntaxKind.NumericLiteral:
139
+ tokens.push(String(Number(token.text.replaceAll('_', ''))))
140
+ continue
141
+ default:
142
+ tokens.push(token.text)
143
+ }
144
+ return tokens
145
+ }
146
+
147
+ /** A style with its whitespace and its `;`s taken out, which CSS reads none of outside a string. */
148
+ function styleShape(style: string): string {
149
+ return style.replace(/[\s;]+/g, '')
150
+ }
@@ -5,6 +5,7 @@
5
5
  // half-made project finishes it rather than overwriting it.
6
6
 
7
7
  import { withoutTrailingSlash } from '#server/withoutTrailingSlash.ts'
8
+
8
9
  import abidePackage from '../../package.json' with { type: 'json' }
9
10
  import { COMPILER_OPTIONS } from './writeDefaultProject.ts'
10
11
 
@@ -39,7 +40,7 @@ const SOURCE_ALIAS = { '#src/*': ['./src/*', './src/*.ts'] }
39
40
  * lands first on the PATH inside `bun run`, shadowing the bun that runs everything else.
40
41
  */
41
42
  export const BUNFIG =
42
- '# 38.122 — a peer is not installed: an installed `bun` would shadow this one inside `bun run`.\n[install]\npeer = false\n\n# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n'
43
+ '# 38.122 — a peer is not installed: an installed `bun` would shadow this one inside `bun run`.\n[install]\npeer = false\n\n# 38.109, 38.119 — a DOM to mount into and measure, then the app\'s `.abide` files as modules.\n[test]\nroot = "src"\npreload = ["@abide/harness/abide/preload"]\n\n# What `bun fmt` and `abide format` print.\n[fmt]\ntabWidth = 4\nsemi = false\nsingleQuote = true\nsortImports = true\n'
43
44
 
44
45
  // WRITTEN, NOT COPIED, beside the manifests: a packer drops a `bunfig.toml` or a `.gitignore` from
45
46
  // the tarball (`bun pm pack` both, `npm pack` the second), so neither survives as a starter file.
@@ -55,6 +56,8 @@ function filesFor(name: string): Record<string, string> {
55
56
  scripts: {
56
57
  // 38.103 — the checker over `.abide`, which `tsc` does not read.
57
58
  check: 'abide check',
59
+ // 38.125 — every `.abide`, then every other file `bun fmt` reads.
60
+ format: 'abide format && bun fmt',
58
61
  dev: 'abide dev',
59
62
  build: 'abide build',
60
63
  start: 'abide start',
@@ -101,16 +104,6 @@ function filesFor(name: string): Record<string, string> {
101
104
  null,
102
105
  4,
103
106
  )}\n`,
104
- 'biome.json': `${JSON.stringify(
105
- {
106
- formatter: { indentStyle: 'space', indentWidth: 4 },
107
- javascript: {
108
- formatter: { quoteStyle: 'single', semicolons: 'asNeeded' },
109
- },
110
- },
111
- null,
112
- 4,
113
- )}\n`,
114
107
  }
115
108
  }
116
109
 
@@ -1,11 +1,16 @@
1
1
  import { existsSync, type FSWatcher, watch } from 'node:fs' // bun exposes no file watcher or sync existence check of its own
2
2
  import { dirname } from 'node:path' // bun exposes no path helpers of its own
3
+
3
4
  import { realOrAsGiven } from '#server/realOrAsGiven.ts'
5
+
4
6
  import { checkSource, lowerApp } from './checkApp.ts'
5
7
  import { citedClauses } from './citedClauses.ts'
6
8
  import { type AppChecker, openChecker } from './compileApp.ts'
7
9
  import { DOCS_SITE } from './DOCS_SITE.ts'
10
+ import { formatAbide } from './formatAbide.ts'
11
+ import { formatDiagnosis } from './formatDiagnosis.ts'
8
12
  import { isNamedAlone } from './isNamedAlone.ts'
13
+ import { lineOf, offsetOf } from './lineOf.ts'
9
14
  import { nearest } from './nearest.ts'
10
15
  import type { Diagnosis, Inspection, Place } from './TypeSession.ts'
11
16
 
@@ -129,8 +134,8 @@ export async function serveLanguage(
129
134
  documents.get(Bun.pathToFileURL(spelled(path)).href)?.text
130
135
 
131
136
  // An app's checker, opened on first use and kept for the session — and TOLD WHAT MOVED ON DISK,
132
- // or it answers every check from the text it read when it opened: an export added to a `.ts`
133
- // beside the page is "no exported member" until the editor restarts this. A project whose
137
+ // or it answers every check from the text it read when it opened: an export added to a `lib`
138
+ // module the page imports is "no exported member" until the editor restarts this. A project whose
134
139
  // tsconfig or package.json moved is opened again.
135
140
  const checkerOf = async (app: string): Promise<AppChecker> => {
136
141
  let checker = checkers.get(app)
@@ -290,6 +295,7 @@ export async function serveLanguage(
290
295
  definitionProvider: true,
291
296
  referencesProvider: true,
292
297
  renameProvider: { prepareProvider: true },
298
+ documentFormattingProvider: true,
293
299
  completionProvider: {
294
300
  triggerCharacters: ['.', '<', '#', ':', '/'],
295
301
  },
@@ -392,8 +398,7 @@ export async function serveLanguage(
392
398
  const found = await references(params)
393
399
  const declarations = (
394
400
  params?.context as
395
- | { includeDeclaration?: boolean }
396
- | undefined
401
+ { includeDeclaration?: boolean } | undefined
397
402
  )?.includeDeclaration
398
403
  const locations: object[] = []
399
404
  for (const place of found?.occurrences ?? [])
@@ -457,6 +462,45 @@ export async function serveLanguage(
457
462
  })
458
463
  return true
459
464
  }
465
+ case 'textDocument/formatting': {
466
+ // 38.130 — the text the editor holds, formatted as `abide format` formats a file:
467
+ // one edit replacing the whole of it, none where nothing moves.
468
+ const uri = (params?.textDocument as { uri: string }).uri
469
+ const document = documents.get(uri)
470
+ if (document === undefined) {
471
+ send({ id: message.id, result: null })
472
+ return true
473
+ }
474
+ const path = Bun.fileURLToPath(uri)
475
+ const text = document.text
476
+ const formatted = await formatAbide(path, text)
477
+ // Shown rather than published: a publish would replace what the check reported.
478
+ for (const finding of formatted.findings)
479
+ send({
480
+ method: 'window/showMessage',
481
+ params: {
482
+ type: 2,
483
+ message: formatDiagnosis(finding, dirname(path)),
484
+ },
485
+ })
486
+ send({
487
+ id: message.id,
488
+ result:
489
+ formatted.text === text
490
+ ? []
491
+ : [
492
+ {
493
+ range: rangeOf({
494
+ path,
495
+ start: { line: 1, column: 1 },
496
+ end: lineOf(text, text.length),
497
+ }),
498
+ newText: formatted.text,
499
+ },
500
+ ],
501
+ })
502
+ return true
503
+ }
460
504
  case 'textDocument/didClose': {
461
505
  const closed = params?.textDocument as { uri: string }
462
506
  if (!documents.delete(closed.uri)) return true
@@ -542,23 +586,6 @@ function wordLength(line: string, character: number): number {
542
586
  return word === undefined ? 1 : word.length
543
587
  }
544
588
 
545
- /**
546
- * The protocol's `{ line, character }`, from 0, as an offset into `text`. A character is a UTF-16
547
- * code unit in the protocol and in a JavaScript string alike.
548
- */
549
- function offsetOf(
550
- text: string,
551
- position: { line: number; character: number },
552
- ): number {
553
- let at = 0
554
- for (let line = 0; line < position.line; line += 1) {
555
- const end = text.indexOf('\n', at)
556
- if (end === -1) return text.length
557
- at = end + 1
558
- }
559
- return at + position.character
560
- }
561
-
562
589
  /** A `Place` at `path`, lines and columns from 1, as the protocol's `Location`, from 0. */
563
590
  function locationOf(path: string, place: Place): object {
564
591
  return { uri: Bun.pathToFileURL(path).href, range: rangeOf(place) }
@@ -4,9 +4,10 @@ import {
4
4
  isVariableDeclarationList,
5
5
  } from 'typescript/unstable/ast/is'
6
6
  import type {
7
- Symbol as CheckedSymbol,
8
7
  Checker,
8
+ Symbol as CheckedSymbol,
9
9
  } from 'typescript/unstable/async'
10
+
10
11
  import { classifyRead, FUNCTIONS } from './classifyRead.ts'
11
12
  import type { Finding } from './Finding.ts'
12
13
  import type { Span } from './projectShadow.ts'
@@ -11,6 +11,7 @@
11
11
  // What no chunk covers is the framework's own scaffolding, and maps to nothing.
12
12
 
13
13
  import { encodeMappings, type Segment } from '#server/sourceMappings.ts'
14
+
14
15
  import type { LoweredChunk } from './Lowering.ts'
15
16
  import type { Origin } from './projectShadow.ts'
16
17
  import { mapBack } from './ShadowMap.ts'
@@ -1,5 +1,7 @@
1
1
  import { SyntaxKind } from 'typescript/unstable/ast'
2
+
2
3
  import { COMPONENT_QUERY, TEXT_QUERY } from '#server/MODULE_QUERIES.ts'
4
+
3
5
  import { isContextualKeyword, type Token, tokenize } from './tokenize.ts'
4
6
 
5
7
  /**
@@ -2,12 +2,12 @@ import type { CheckerAnswer } from './CheckerAnswer.ts'
2
2
  import type { Read } from './classifyRead.ts'
3
3
  import { isComponentTag } from './classifyTemplate.ts'
4
4
  import { isNamedAlone } from './isNamedAlone.ts'
5
- import { Lowering } from './Lowering.ts'
6
5
  import { diagnosisAt } from './lineOf.ts'
6
+ import { Lowering } from './Lowering.ts'
7
7
  import type { AbideFile } from './parseAbide.ts'
8
8
  import {
9
- type Body,
10
9
  bodiesOf,
10
+ type Body,
11
11
  type ForBlock,
12
12
  type TemplateNode,
13
13
  visitBodies,
@@ -14,6 +14,7 @@ import {
14
14
  isPropertyAccessExpression,
15
15
  isWhileStatement,
16
16
  } from 'typescript/unstable/ast/is'
17
+
17
18
  import { FUNCTIONS, TRANSPARENT } from './classifyRead.ts'
18
19
  import type { Finding } from './Finding.ts'
19
20
  import type { PositionKind, PositionTable } from './PositionTable.ts'
@@ -11,6 +11,7 @@
11
11
  import { mkdtemp, rm } from 'node:fs/promises' // bun exposes no temporary-directory creation or recursive removal of its own
12
12
  import { tmpdir } from 'node:os' // bun exposes no temporary-directory path of its own
13
13
  import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
14
+
14
15
  import { nearest } from './nearest.ts'
15
16
 
16
17
  /** What every abide app in the docs and the scaffold is written against. */
@@ -33,7 +34,10 @@ export async function writeDefaultProject(
33
34
  await Bun.write(
34
35
  tsconfig,
35
36
  JSON.stringify({
36
- compilerOptions: { ...COMPILER_OPTIONS, paths: await aliasPaths(root) },
37
+ compilerOptions: {
38
+ ...COMPILER_OPTIONS,
39
+ paths: await aliasPaths(root),
40
+ },
37
41
  include: [`${root}/**/*`],
38
42
  }),
39
43
  )
@@ -1,3 +1,3 @@
1
1
  // The `abide-bridge` release a pairing command names, so a visitor's `bunx` runs the relay this
2
2
  // server speaks. `packages/abide/tests/claudeCode.test.ts` holds it to the bridge package's own.
3
- export const BRIDGE_VERSION = '0.3.4'
3
+ export const BRIDGE_VERSION = '0.3.5'
@@ -18,7 +18,9 @@
18
18
  // to this one rather than registering a plugin nobody asks.
19
19
 
20
20
  import { basename, dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
21
+
21
22
  import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
23
+
22
24
  import type { ServerTable } from './answerRequest.ts'
23
25
  import type { MountRow } from './answerRpc.ts'
24
26
  import {
@@ -7,6 +7,7 @@ import { type Failed, problemOf } from '#shared/Failed.ts'
7
7
  import { RENDER_LOG } from '#shared/log.ts'
8
8
  import { coversPrefix, partsOf } from '#shared/matchRoute.ts'
9
9
  import { coversRoute, parseRoutePattern } from '#shared/routePattern.ts'
10
+
10
11
  import type { AppSources } from './answerRequest.ts'
11
12
  import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
12
13
  import { boundPage, renderDocument } from './render.ts'
@@ -19,23 +19,23 @@ import {
19
19
  } from '#shared/ADDRESSES.ts'
20
20
  import { APP_META } from '#shared/APP_META.ts'
21
21
  import { DEBUG_META } from '#shared/DEBUG_META.ts'
22
- import { ICON_ADDRESSES } from '#shared/LAUNCHER_FILES.ts'
23
- import { OFFLINE_META } from '#shared/OFFLINE_META.ts'
24
22
  import { decodedOrKept } from '#shared/decodedOrKept.ts'
25
23
  import { ERROR_META } from '#shared/ERROR_META.ts'
26
24
  import { escapeAttribute } from '#shared/escapeText.ts'
27
25
  import { type Failed, isFailed } from '#shared/Failed.ts'
28
26
  import { HandlerRoute } from '#shared/HandlerRoute.ts'
27
+ import { health } from '#shared/health.ts'
29
28
  import { KEEP_HEADER } from '#shared/KEEP_HEADER.ts'
30
29
  import { KEEP_OPENED } from '#shared/KEEP_OPENED.ts'
31
- import { health } from '#shared/health.ts'
30
+ import { ICON_ADDRESSES } from '#shared/LAUNCHER_FILES.ts'
32
31
  import { appName } from '#shared/log.ts'
33
- import { compose } from '#shared/Middleware.ts'
34
- import { MOUNT_META } from '#shared/MOUNT_META.ts'
35
32
  import { matchOf, type Params, partsOf } from '#shared/matchRoute.ts'
33
+ import { compose } from '#shared/Middleware.ts'
36
34
  import { MOUNT, mounted } from '#shared/mount.ts'
35
+ import { MOUNT_META } from '#shared/MOUNT_META.ts'
37
36
  import { NAVIGATION_HEADER } from '#shared/NAVIGATION_HEADER.ts'
38
- import { refuse, notFound as refusedNotFound } from '#shared/refuse.ts'
37
+ import { OFFLINE_META } from '#shared/OFFLINE_META.ts'
38
+ import { notFound as refusedNotFound, refuse } from '#shared/refuse.ts'
39
39
  import { arrive } from '#shared/route.ts'
40
40
  import {
41
41
  parseRoutePattern,
@@ -44,29 +44,30 @@ import {
44
44
  } from '#shared/routePattern.ts'
45
45
  import type { Method, Rpc } from '#shared/rpc.ts'
46
46
  import { rpcRequest } from '#shared/rpcRequest.ts'
47
+
47
48
  import { answered, answerFailure } from './answerFailure.ts'
48
49
  import {
49
50
  answerRpc,
50
51
  type Mounted,
51
- type MountRow,
52
52
  mountedOwn,
53
53
  mountHandlers,
54
+ type MountRow,
54
55
  } from './answerRpc.ts'
55
56
  import { answerBlobTagged, answerValue, contentEtag } from './answerValue.ts'
56
- import { answerBridge } from './bridgeRelay.ts'
57
- import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
58
- import { appManifest } from './appManifest.ts'
59
57
  import { answerWorker } from './answerWorker.ts'
58
+ import { appManifest } from './appManifest.ts'
59
+ import { answerBridge } from './bridgeRelay.ts'
60
60
  import { callerOrMint } from './callerOf.ts'
61
- import { settingOn } from './config.ts'
61
+ import { CLIENT_ENTRY } from './CLIENT_BUILD.ts'
62
62
  import { compressAnswer } from './compressAnswer.ts'
63
- import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
64
- import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
63
+ import { settingOn } from './config.ts'
65
64
  import { noteCallInRender } from './deferredBuilds.ts'
65
+ import { DOCUMENT_HEADERS } from './DOCUMENT_HEADERS.ts'
66
66
  import { answerMcp, openApi } from './generatedSurfaces.ts'
67
67
  import { type AppRung, failureOf, rungsOver } from './hooks.ts'
68
68
  import { chunksOf, type Manifest } from './manifestOf.ts'
69
69
  import { muxAdmission } from './muxUpgrade.ts'
70
+ import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
70
71
  import { plainAnswer } from './plainAnswer.ts'
71
72
  import { prefers } from './prefers.ts'
72
73
  import { PRINCIPAL_MOUNT } from './principal.ts'
@@ -78,8 +79,8 @@ import {
78
79
  renderDocument,
79
80
  } from './render.ts'
80
81
  import {
81
- type AnsweringApp,
82
82
  adoptRequest,
83
+ type AnsweringApp,
83
84
  beginRender,
84
85
  currentRequestScope,
85
86
  type PageRender,
@@ -100,6 +101,8 @@ type ServerRoute = {
100
101
  // 38.28 — the modules a browser loads at this address, the page's then its layouts', as keys
101
102
  // into the manifest `manifestOf` reads.
102
103
  clientModules: string[]
104
+ // 23.26 — each matcher its pattern names, to the anchored source the build resolved it to.
105
+ matchers: Record<string, string>
103
106
  }
104
107
 
105
108
  // 35.2 — an error page, and the directory it answers for.
@@ -122,6 +125,7 @@ type Ranked = RankedError & {
122
125
  */
123
126
  type RouteRow = {
124
127
  pattern: string
128
+ matchers: Record<string, string>
125
129
  segments: Segment[]
126
130
  page: Ranked | undefined
127
131
  handlers: Map<Method, Mounted>
@@ -178,6 +182,16 @@ export type AppSources = {
178
182
  // 41.29 — the seed sink of a render no page hydrates: every call is left to run when it is made.
179
183
  const SEEDS_NOTHING: PageRender['seed'] = () => false
180
184
 
185
+ // 35.19 — what fetch calls a navigation request's destinations, and a script's `fetch`.
186
+ const PAGE_DESTINATIONS = new Set([
187
+ 'document',
188
+ 'embed',
189
+ 'frame',
190
+ 'iframe',
191
+ 'object',
192
+ 'empty',
193
+ ])
194
+
181
195
  /** The `Response` to one request: a handler, a surface of abide's own, a file, or a page. */
182
196
  export async function answerRequest(
183
197
  app: AppSources,
@@ -331,6 +345,17 @@ async function answerMatched(
331
345
  headers: { 'cache-control': 'no-store' },
332
346
  })
333
347
 
348
+ // 35.19 — A REQUEST THAT CANNOT DISPLAY A PAGE, such as the `/favicon.ico` a browser asks every
349
+ // site for where a `[param]` page matches it: refused before setup, so no handler runs for it
350
+ // and no caller is minted. Bare, because an error page would set up too and nothing shows it;
351
+ // `no-store`, because the address answers a navigation with the page.
352
+ const destination = request.headers.get('sec-fetch-dest')
353
+ if (destination !== null && !PAGE_DESTINATIONS.has(destination))
354
+ return new Response(null, {
355
+ status: 404,
356
+ headers: { 'cache-control': 'no-store' },
357
+ })
358
+
334
359
  // 33.4 — a prop is an ACCESSOR, which is the contract `renderComponent` renders a child under
335
360
  // and the one the client router mounts a page under; the params are the record `firstMatch`
336
361
  // hands the client for this same address.
@@ -534,16 +559,22 @@ export async function appSources(parts: SourceParts): Promise<AppSources> {
534
559
  pattern: route.pattern,
535
560
  load: route.load,
536
561
  layouts: route.layouts,
562
+ matchers: route.matchers,
537
563
  document: documentOf(route.clientModules),
538
564
  })
539
565
  // 19.1 — the build refused two files at one route and method, so a route is one row however
540
- // many methods answer at it.
566
+ // many methods answer at it — and 23.27 one matcher per position, so whichever file opens the
567
+ // row brings what every file at it would.
541
568
  const rows = new Map<string, Omit<RouteRow, 'segments'>>()
542
- const rowAt = (pattern: string): Omit<RouteRow, 'segments'> => {
569
+ const rowAt = (
570
+ pattern: string,
571
+ matchers: Record<string, string>,
572
+ ): Omit<RouteRow, 'segments'> => {
543
573
  let row = rows.get(pattern)
544
574
  if (row === undefined) {
545
575
  row = {
546
576
  pattern,
577
+ matchers,
547
578
  page: undefined,
548
579
  handlers: new Map(),
549
580
  answers: new Map(),
@@ -552,12 +583,19 @@ export async function appSources(parts: SourceParts): Promise<AppSources> {
552
583
  }
553
584
  return row
554
585
  }
555
- for (const page of rankRoutes(pages)) rowAt(page.pattern).page = page
586
+ for (const page of rankRoutes(pages))
587
+ rowAt(page.pattern, page.matchers).page = page
556
588
  for (const handler of mounted.handlers.values())
557
- rowAt(handler.address).handlers.set(handler.method, handler)
589
+ rowAt(handler.address, handler.matchers).handlers.set(
590
+ handler.method,
591
+ handler,
592
+ )
558
593
  const errors: RankedError[] = []
559
594
  for (const error of parts.table.errors)
560
- errors.push({ ...error, segments: parseRoutePattern(error.pattern) })
595
+ errors.push({
596
+ ...error,
597
+ segments: parseRoutePattern(error.pattern, error.matchers),
598
+ })
561
599
  errors.sort((a, b) => b.segments.length - a.segments.length)
562
600
  const sources: AppSources = {
563
601
  routes: rankRoutes([...rows.values()]),
@@ -11,15 +11,16 @@ import { GLOBAL_GETS } from '#shared/GLOBAL_GETS.ts'
11
11
  import { isRecord } from '#shared/isRecord.ts'
12
12
  import type { JsonValue } from '#shared/JsonValue.ts'
13
13
  import { mounted as underMount } from '#shared/mount.ts'
14
- import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
15
14
  import type { Gated } from '#shared/reactive/argsContract.ts'
16
15
  import { deadlineOf, heldGenerationOf } from '#shared/reactive/index.ts'
17
16
  import type { Defaults } from '#shared/reactive/parameterDefaults.ts'
18
17
  import { notFound, refuse } from '#shared/refuse.ts'
19
18
  import type { HeldToAnnotation, Method, Rpc } from '#shared/rpc.ts'
19
+ import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
20
20
  import { argsInURL } from '#shared/rpcRequest.ts'
21
21
  import { VERSION_HEADER } from '#shared/VERSION_HEADER.ts'
22
22
  import { versionInEtag } from '#shared/versionInEtag.ts'
23
+
23
24
  import {
24
25
  answered,
25
26
  answerFailure,
@@ -29,7 +30,6 @@ import {
29
30
  import { followAsEvents } from './answerSocket.ts'
30
31
  import { answerValue, namesEtag, sseHeaders } from './answerValue.ts'
31
32
  import { argsFor } from './argsFor.ts'
32
- import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
33
33
  import { callerOf } from './callerOf.ts'
34
34
  import {
35
35
  argsDocumentOf,
@@ -45,6 +45,7 @@ import {
45
45
  crossingRefusal,
46
46
  } from './crossingOrigin.ts'
47
47
  import { type AppRung, failureOf, rungsOver } from './hooks.ts'
48
+ import { OFFLINE_STALE } from './OFFLINE_STALE.ts'
48
49
  import { prefers } from './prefers.ts'
49
50
  import {
50
51
  currentRequestScope,
@@ -72,6 +73,8 @@ export type Mount = {
72
73
  export: string
73
74
  /** The route its hooks are filed at, which its rungs are read over (37.1, 16.72). */
74
75
  scope: string
76
+ /** 23.26 — each matcher its address names, to the anchored source its row is ranked with. */
77
+ matchers: Record<string, string>
75
78
  /** Whether it is a private handler, listed nowhere (16.71). */
76
79
  private: boolean
77
80
  /** Whether a browser is handed a wrapper for it. 20.4. */
@@ -154,6 +157,7 @@ export function mountedOwn(
154
157
  name,
155
158
  export: name,
156
159
  scope: '/',
160
+ matchers: {},
157
161
  private: false,
158
162
  ui: false,
159
163
  cli: false,