Repository navigation
Make Desktop own durable project changes - #4
Merged
Merged
Conversation
…ed in The changelog stopped at wave C2 and never covered the 56 commits after it, which is most of what the language now is. This writes them up under 0.10.0 rather than under a new version, because 0.10.0 was finalized eleven minutes after the last of those commits landed: they are in the tree it releases, not in a follow-up to it. Nothing was ever published, so there is no released version to break from, and the two changes that read as breaking — computed becoming a declaration, props becoming mutable — are simply what the first release contains. The reactive-grid entry is written as one story because it is one defect. The owner reported it twice from two directions without the two looking related: a const that froze a reactive read, and a watch body that could never run. Both came from state being a declaration while computed was a function, which forced every derived value into a const — and const is the thing that freezes. The entry says that, because a changelog that lists the two diagnostics separately would hide the reason either exists. Two numbers I would have written from memory turned out wrong, so they are written from the record instead. The derived package set did not catch a list that had drifted — both copies in the repo were correct, and the failure was outside it, in a brief I truncated to six of eight names. And the tour did not surface five formatter defects; it surfaced ten findings, of which the two that matter are a formatter that broke compiling source and an emitter that passed check and failed build. Verified with check:docs, which exits 0 and reports 79 of 110 fragments not checked in full — the changelog itself adds no fences, which is the D70/D71 mistake not repeated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The headline claimed a division of labour — AI writes, humans read and own — and the owner's verdict was that it did not express what the language is. He is right, and the reason is that a division of labour is downstream: being extensible with the framework unified into the language is what the thing is, one language covering markup, styling, state, tests, and the server is what that buys, and AI writing the code is what makes the resulting strictness and the refusal of compatibility affordable. The headline had the last link where the first belongs. Two corrections of mine are recorded in D78 rather than quietly fixed. My first attempt promoted the owner's founding motivation — that nobody using Vel should have to design its replacement — into the headline, and he answered that this is his motivation, not the language's positioning; the two are different questions and the document now gives each its own section. My second attempt wrote "language framework", which reads as a layer sitting on top of a language, when the fact is the opposite: component, state and look are keywords, not imports. The positioning was never new. Core knowing nothing about the DOM, stylesheets, the filesystem or windows is the thing every boundary ruling this session was defending — unsafe css belonging to the Web extension rather than Core, Core's own rosters, the per-extension surface audit. Unification shrinks what has to be verified to one language; extensibility is what keeps that from turning Core into the pile it was built to escape. Neither half stands alone, so D78 states them as one claim. why-velarscript.md is rebuilt on the eight questions the owner asked in order, including the last one, which is the one that matters: what it does not promise. Never backward compatibility, no stable channel yet, ecosystem breadth costs you a declared boundary, no new runtime, and migration is not free. A positioning document that lists only promises is an advertisement. check:docs passes: 211 examples, unchanged fence counts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ates Section 1 still said the language exists so AI can write code a human owner can read — the framing D78 replaced. What makes the substitution easy is that rule 5 has always been the other half of the new positioning: keep target capabilities in explicit extensions rather than hiding Web, Node, or Desktop behavior in Core. The charter was enforcing extensibility as a rule while the prose above it advertised a division of labour. So the opening now says both halves and says why they are one: unification puts markup, styling, state, tests, and the server in a single checked language, and that is only affordable because capability lives in extensions instead of accumulating in Core. Rule 5 is labelled as positioning rather than hygiene so a future editor does not treat it as an implementation detail and relax it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tour coverage gate had eighteen categories and every one of them was a name — keywords, module exports, Look properties, namespace members. A syntactic form built out of already-covered keywords was therefore invisible to it, which is how D53's inline foreign-source blocks shipped with sixteen unit tests and not one use in any .vel file in the repository. `extern`, `js` and `unsafe` were all observed by `extern module` and `import js unsafe` in tour chapter 13, so the gate stayed green over a construct nobody had written. The new category derives from CoreStatement, the union parseStatement returns, through a mapped type placed beside it. That turns "a new declaration form must join this roster" into "it cannot compile otherwise" — verified by adding a member and watching tsc reject the roster rather than by trusting the shape. The parser dispatch was rejected as the authority because it is an if-chain rather than a table, and the emitter switch because it is not enumerable at run time. Extension statements never join that union, so they reach the roster through a new syntax slot in the extension protocol, and an extension that registers a parser while publishing no constructs is itself a gate failure — syntax cannot arrive invisibly. Three red-proofs, each deleting something different from a copy of the tour: the inline JavaScript blocks, the inline CSS block with its `import css unsafe` siblings left in place, and the Web extension's syntax slot. All three name what is missing. The second is the interesting one: `unsafe css` shares a node kind with the external import and is still separately required, because its source is a tagged union. That is also the limit, and the gate prints it rather than hiding it. `extern js` and `unsafe js` are one node told apart by a boolean, as are const/let, def/async def, class/abstract class, for/async for. The category can only require the kind, so deleting one of a pair would not go red. Closing it means giving those nodes a discriminated form, which is an AST change and needs a ruling, not a gate edit. The tour now writes all three blocks for real: a captured `extern js` reaching `crypto.getRandomValues` — a global has no package, so before D53 there was no spelling for it at all — an `unsafe js` reading process internals, and an `unsafe css` for paged media, which Look excludes by design so nothing is being worked around. The wave also found that the blocks were documented nowhere: zero hits across the charter, escape hatches, the JavaScript bridge and the skill brief, with the charter's own module-boundary list omitting them. A construct with no prose is effectively unshipped when the KPI is blind writability, so the charter gains a specification and the escape-hatch playbook gains the case these blocks exist for — JavaScript with no package to import from — including the honest limit that a captured block cannot use top-level await. One repair of mine: the positioning commit pushed the skill brief eight lines over its 750-line budget and turned two tests red. The wave proved the provenance with a pristine HEAD snapshot rather than assuming it. Fixed by cutting the opening back to its original size, not by raising the ceiling, which is what the assertion asks for. All four gates: check=0, test=0 (1438/1438), test:packages=0, test:browser=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…than hidden Neither blocks the release, and that is the point: the gate prints what it cannot reach instead of routing it through the exemption table, so there is no green that claims more than it checked. Rule 198 is granularity. Five node kinds carry two spellings each — extern js against unsafe js, const against let, def against async def, class against abstract class, for against async for — and no compiler-owned table enumerates those splits, so the category can only require the kind. The Web extension already shows the fix working: unsafe css shares a node with the external import and is still separately required, because its source is a tagged union. I recommend doing it, and I split the recommendation, because the honest cost is not one union: four of the five are core nodes whose discriminants would touch parsing, types, emission and the formatter. The embedded-JavaScript node is the cheap one and the only one with demonstrated need, so it stands alone as a first step and the other four are a separate argument. Rule 199 is a defect that happens to block the expression-side version of this work. Core's own parser produces ExtensionExpression:core:duration as a bare `as Expression` cast with no interface and no union membership, so a roster mapped over CoreExpression would silently miss it — the expression category would have been false green, which is why the wave scoped itself to statements and said so. Worth fixing whether or not expressions ever get covered: an undeclared node produced by the compiler's own parser is the hand-maintained vocabulary family showing up in the AST. The negative result is recorded too, because it is the useful half: every other expression, pattern, type-syntax and class-body kind is already exercised by the tour, and the one exception is a parse-recovery node the AST itself documents as never reaching code generation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wave W1 hit both while writing the website against the real compiler, and both had the charter contradicting itself within twenty lines while the compiler agreed with the later half. The reserved globals were still listed as computed, mount and tick. They are cached, mount and tick — computed became a contextual keyword and cached took the reserved slot, which the bullet list twelve lines below already said correctly. A charter that names the wrong reserved word is worse than silence, because refusing `const cached = 1` is exactly the kind of rejection a reader would come here to understand. The contextual-keyword count and roster were each one short: eleven words, and computed is the missing one. It is worth stating explicitly rather than just adding to the list, because computed is the word most likely to be assumed reserved now that it declares — it declares in its own shape and is an ordinary name everywhere else, like the other ten. Section 3 now names WEB_CONTEXTUAL_KEYWORDS as the owner of that roster, the way the sentence beside it already names CORE_CONTEXTUAL_KEYWORDS. The prose is still a copy rather than a derivation and nothing gates it, which is how it drifted; naming the authority at least tells the next editor where the truth lives. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ists Two failures, and they are the same shape twice: an assertion written as "exists" where the truth needed "exactly". The website pins @velarscript/cli 0.10.0, nothing by that name is published, and what is actually installed comes from a manual bootstrap against local source. So the compiler in node_modules stops at whoever last ran that command, and nothing watches it. Swapping in a current build turned samples:check red on sixteen samples at once — eleven stale spellings, an inverted counter-example publishing the correct spelling as an error, a type-name collision, an enum that had grown two members, and three D74 props cases where the prose was wrong and not only the code. Those three are the reason this is filed as severe: the site was publishing an unconditional guarantee the language had deliberately withdrawn, and a stale guarantee is a promise the language no longer keeps. The second is narrower and older. A counter-example passes when the codes it quotes appear, not when they are the only ones emitted, so one sample taught VEL4001 while silently also emitting VEL5065. Worse, a sample can be carried by a neighbour: one published VEL3007 for a rejection the compiler stopped making, and stayed green because a different line in the same block still produced that code. Set equality closes both. That makes three appearances of this family — the standard-module surface audit was first, where a substring assertion pretended to resolve links. Worth naming, because it keeps arriving disguised as a different bug. My recommendation on the toolchain is to make sameness a gate rather than a step: assert the installed compiler and the language repo agree before samples:check runs. Publishing to npm and pinning a real version does not fix it — it only makes the drift better dressed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The adversarial audit found a filename with a legal `)` in it walking straight
past the relative-address gate: `url("./mark).svg")` checked clean, built
clean, emitted no diagnostic, and shipped a stylesheet into dist/assets/ whose
address resolves one directory away from the file. Measured, not reasoned —
root asset 200, CSS-resolved asset 404. The same regex refused a real value in
the other direction, rejecting `content: "url(./not-an-asset.svg)"`, which is
text rather than a reference.
Both directions from one cause: the scanner treated a quote-blind `[^"')]+` as
CSS. Patching the character class would only move the next shape, and the two
findings are already that argument — one regex, wrong twice, in opposite
directions.
So the address gate now reads a CSS Syntax Level 3 tokenizer. Strings are
skipped as strings, comments as comments, escapes are decoded, and bad-url and
bad-string recover where the specification says a browser drops the
declaration. It reports the address a browser would actually request rather
than the source text, which is why two findings are about the gate naming the
wrong file rather than missing it.
Tokenizing surfaced twenty more shapes the regex got wrong, in all three
classes: six silent bypasses of the same severity as the reported one, two
where the gate fired and named a truncated or still-escaped address, and twelve
real values wrongly refused — among them `myurl(`, which is not a url token at
all, and an escape sequence spelling an absolute path.
The @import check was a second hand-written scanner in the same file with lower
coverage than the first, which is the derived-vocabulary family again: two
copies of one job, and the copy drifts. It reads the same token stream now, and
that alone fixed two more false negatives — an at-rule with no separator in
front of it, and an escaped at-keyword.
The regression pins token grammar rather than adding samples, runs every case
through both the inline and external CSS sources, and asserts the built
artifact: a refused build leaves no dist at all, and an accepted one is
re-tokenized so that every address in the emitted stylesheet is root-absolute,
a fragment, or schemed, and every root address exists on disk.
All four gates green: check=0, test=0 (1443/1443), test:packages=0,
test:browser=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tokenizing CSS made two more spellings visible that the regex could never have shown: src() in @font-face, and the bare string arguments of image-set. Both carry relative addresses, both get extracted into assets/, and both 404 there exactly the way url() did. The rule's own diagnostic says why it exists — so extracted asset ownership stays explicit — and asset ownership cannot tell those spellings apart. The argument already covered them; only the wording stopped at a function name. That is the same failure as publishing a name nobody can reach, from the other side: a rule that does not reach a position where its own reason applies lets an author write something that genuinely breaks while the tool says nothing. The ruling carries a guard rather than a list to implement. The spellings came from the wave's tokenizer observing them, not from my checking what browsers actually request, so each has to be confirmed writable before it is covered, and any that cannot be confirmed stays out and gets said so. A rule that forbids a spelling nobody can write is the unreachable-name defect wearing the opposite coat. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The adversarial audit found `try await import("./dep.vel")` answering false for
a module that exists — clean check, exit 0, and a build with no dep.js in it.
Same cause in a `using` initializer, a test body, a class getter, `@dispose`
and `@iterate`, where it surfaces as a runtime module-not-found instead.
dependenciesOf carried a second hand-written switch over statement and
expression kinds. That is the derived-vocabulary family living in the module
graph, and it was already drifting from work landed the same day: `@iterate`
shipped this morning and the switch had never heard of it.
My brief was wrong about the repair and the wave said so instead of following
it. I asserted the AST had a general visitor to reuse; it does not. What exists
is the ContainsDirectAwait family, which does have cases for the very
containers the audit named — probably what the audit saw — but answers a
different question and is deliberately wrong for this one: a test body returns
false because it is its own async frame, and so does an arrow function.
Reusing it would have rebuilt A-010 with a fresh set of holes.
So dependency discovery no longer walks by kind at all. The AST gained a
structural descent that never asks what a node is, and the lookup is a filter
over it. A container added tomorrow needs no edit anywhere, which a test pins
directly with a synthetic extension node of an unknown shape nested three deep.
The same defect had a second copy in the extension protocol —
CompilerDependencyContext and the two visitDependency hooks let every extension
keep its own hand-maintained mirror of the AST, each free to drift
independently. Removed, with the Web implementation behind it. Net -193/+80.
The regression is a matrix rather than a sample, because "one example exists"
is exactly what let this through. Every distinct expression position in the
tour and app corpus is rewritten into a dynamic import and re-inspected — 2445
positions — and the assertion needs no notion of what a container is: whatever
node the parser produced, the graph must have it. The one position that refuses
quotes the language's own diagnostic as its reason, so the single exclusion is
not mine. A generated project then carries a distinct module through 34 core
containers under check, run, build and test.
Under the old code that matrix misses 240 chains, and every one attributes to
the six containers the audit named. No seventh — recorded as a result rather
than as an absence.
All four gates green: check=0, test=0 (1460/1460), test:packages=0,
test:browser=0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eft open The changelog gains the audit itself, not just its repairs. A release that was attacked before shipping should say so, and say what the attack found: 26 findings, five that compiled clean and answered wrong. The three gate failures are grouped deliberately — a gate that overstates its reach is worse than a missing one, because it is trusted, and all three were built during this same release. D82 records what the traversal wave left open. The one decision is the asymmetry it exposed: the statement roster is complete by construction, since its mapped type makes tsc refuse an incomplete copy, while the expression side has no roster at all and the test hand-types nineteen kinds. Adding an expression kind would redden nothing — it would be silently classified as not an expression and drop out of coverage. I recommend publishing the roster: the runtime fix is safe either way, but the gate's completeness now rests on a copy, which is the exact family this wave existed to remove. Three things are recorded so they are not rediscovered or "fixed" later. The remaining hand-maintained switch with a silent default is named as the next place to audit, without being claimed as a defect, because the wave could not construct a live failure and said so. A container that appears only in shorthand is invisible to a corpus-derived roster — the real limit of deriving from the tour, now written into the test rather than left as folklore. And static imports still scan only the top level, which is correct and load-bearing: a nested import is refused by the language, so it can never need to be in the graph. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ter copy All three gates were built during this release and all three overstated their reach, which is worse than not having them, because a gate is trusted. The tour gate credited a name the moment its import specifier parsed. Keeping the import and deleting the only call still reported full coverage, next to a failure message that says "no module uses it". A named import now counts only when the analyzer resolved a reference to its local binding, and an import that is never referenced is named as the dead line it is. Both directions are tested, which is the part that was missing: the old suite only ever deleted imports, so it could never have found this. That fix uncovered a fourth instance in the same file, and it contradicts a comment I had just written asserting it was impossible: namespace members were credited by the import's local spelling rather than its resolved symbol, so a local object literal with the right field name forged coverage for a standard module export. Both maps are keyed by symbol id now. The documentation gate's fence extractor required column zero, backticks only, and a closing fence exactly as long as the opening — three things CommonMark does not say. It now implements §4.5, including dedenting the opening indentation off every content line, which is load-bearing for an indentation-sensitive language. Where the scanner genuinely cannot reach — a fence inside a block quote or a deeply nested list — it names the line and fails rather than skipping in silence. Exactly one fence in the tree was invisible to the old regex, it compiles clean, and no prose needed changing. The package gate derived its pack list and nothing after it: content checks, the install list, and the build chain each walked their own literals, and a fifth copy sat in the installed-browser acceptance where a new package with no dependents would have been packed and imported by one job and silently skipped by the other. All of them read the derived set now, and the contract asked of a package comes from its own manifest — every path promised through main, types, exports, bin and velar.entry must be in the tarball, and every published specifier must actually import in a clean consumer. Two latent bugs fell out. The documentation gate declared its failures array after the walk that pushes into it, so a checkout with no README crashed instead of reporting. And the build order is now derived from each manifest's own workspace dependencies rather than a hand-written chain. All four gates green: check=0, test=0 (1462/1462), test:packages=0, test:browser=0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Dependency boundary
The borrowed Platform package owns framework-neutral transaction mechanics. VelarScript still owns language semantics, permissions, Desktop host contracts, and the user-visible capability surface. The package is installed from GitHub Packages through an environment-token npm configuration; no credential is committed.
Validation