JavaScript
50 files, no framework, no build step, and a component system nobody has written down. This is the most original and least documented convention in the estate, and reconstructing it by counting is most of what the survey behind this page did.
There is not one documented rule for JavaScript. All 31 written rules are about Python and process. Everything on this page is a convention discovered in the code and measured — which means it is currently maintained by whoever last read the neighbouring file. The rules that do not exist →
1. The complete example
components/sg-compute/sg-compute-left-nav/v0/v0.1/v0.1.0/sg-compute-left-nav.jsverbatim · 02__javascript.md/**
* sg-compute-left-nav — vertical icon-rail navigation for the admin dashboard.
*
* Items: Compute / Storage / Settings / Diagnostics.
* Click fires sp-cli:nav.selected { view } on document.
*
* @module sg-compute-left-nav
* @version 0.1.0
*/
import { SgComponent } from 'https://dev.tools.sgraph.ai/components/base/v1/v1.0/v1.0.0/sg-component.js'
class SgComputeLeftNav extends SgComponent {
static jsUrl = import.meta.url
get resourceName() { return 'sg-compute-left-nav' }
get sharedCssPaths() { return ['https://dev.tools.sgraph.ai/components/tokens/v1/v1.0/v1.0.0/sg-tokens.css'] }
onReady() {
this._current = 'compute'
this.shadowRoot.querySelectorAll('.nav-item').forEach(btn => {
btn.addEventListener('click', () => this._select(btn.dataset.view))
})
this._update()
}
_select(view) {
if (view === this._current) return
this._current = view
this._update()
document.dispatchEvent(new CustomEvent('sp-cli:nav.selected', {
detail: { view },
bubbles: true, composed: true,
}))
}
_update() {
this.shadowRoot.querySelectorAll('.nav-item').forEach(btn => {
btn.classList.toggle('selected', btn.dataset.view === this._current)
btn.setAttribute('aria-current', btn.dataset.view === this._current ? 'page' : 'false')
})
}
}
customElements.define('sg-compute-left-nav', SgComputeLeftNav)Apache-2.0 — quoted from the estate's own source, not covered by this site's CC BY 4.0. Counted 2026-08-24.
2. The component system
Native web components.
41
customElements.define calls across
50 files. No React, no Vue, no bundler, no
build step. The browser is the runtime.
The three-file triplet
Every component is exactly three files, same basename, same directory:
sg-compute-left-nav.js behaviour
sg-compute-left-nav.html markup
sg-compute-left-nav.css stylesApache-2.0 — quoted from the estate's own source, not covered by this site's CC BY 4.0. Counted 2026-08-24.
static jsUrl = import.meta.url is what makes this work: the component knows its
own URL, so the base class can fetch the sibling .html and .css
without anything being told where they live.
Self-locating components are the mechanism that removes the build step.
The versioned CDN path
https://dev.tools.sgraph.ai/components/<name>/v1/v1.0/v1.0.0/<file>.js
^^^ ^^^^ ^^^^^^
major minor patch — as directoriesApache-2.0 — quoted from the estate's own source, not covered by this site's CC BY 4.0. Counted 2026-08-24.
Three nested directories, one per semver level. A consumer pins at whatever depth it wants
stability: /v1/ follows the major, /v1.0/ follows the minor,
/v1.0.0/ is frozen. Immutable URLs, no lockfile, no node_modules,
cacheable forever. The same scheme is used locally under components/.
What the CDN serves today: SgComponent (the base class),
sg-tokens.css (design tokens), sg-vault-client.js and
sg-vault-write.js.
The host in those examples is published deliberately. Every component imports its
base class and its tokens from dev.tools.sgraph.ai, so a component example with
the import URL redacted teaches nothing. It is a public CDN serving public component code and
it is visible in any rendered page's network tab. The concern worth stating is the
dev. prefix: this page is documenting a development host as a canonical public
contract. Q4 →
The base-class contract
SgComponent supplies the lifecycle; a component overrides four things:
| Member | Purpose |
|---|---|
static jsUrl = import.meta.url | Self-location. Required — without it the component cannot find its own markup. |
get resourceName() | The basename of the sibling .html and .css. |
get sharedCssPaths() | Tokens and shared sheets to adopt. |
onReady() | The lifecycle hook — not connectedCallback directly. |
onReady() rather than connectedCallback is the tell. The base class
handles the async fetch of the sibling files and calls onReady() once the shadow
root is populated, so a component never has to think about whether its markup has arrived. It
is a small API decision that removes a whole class of race condition, and it is exactly the kind
of thing a lint rule should enforce.
The rule that would →
6 files call
attachShadow directly; the rest inherit it from the base class, and every component
addresses its own markup through this.shadowRoot.
3. Formatting, measured
| Convention | Evidence |
|---|---|
| 4-space indent | 3,791 indented lines are a multiple of 4; 148 are not |
| Single quotes | 4,006 single vs 400 double — 91% |
| No semicolons | 531 statement lines without vs 401 with. Split, and it is a real inconsistency — see below |
| Trailing commas | In multi-line object and array literals, consistently |
| Aligned object keys | detail: { view }, — the same discipline as Python and CSS |
_ prefix for private | Universal — and directly against Python rule 9 |
| ESM everywhere | 48 type="module" script tags. No UMD, no globals |
| Banner comments | 15 of 50 files open with one, in two different styles |
4. Events, state and data
Events are namespaced and go through document:
components/sg-compute/sg-compute-left-nav/…/sg-compute-left-nav.jsexcerpt · 02__javascript.mddocument.dispatchEvent(new CustomEvent('sp-cli:nav.selected', {
detail: { view },
bubbles: true, composed: true,
}))Apache-2.0 — quoted from the estate's own source, not covered by this site's CC BY 4.0. Counted 2026-08-24.
bubbles: true, composed: true is what lets an event escape the shadow root. The
namespace is sp-cli: — the old CLI name — and it appears in
23 files. That is a rename
surface nobody had counted, and renaming an event namespace is a breaking change for any
listener outside the repository.
State is instance fields, _-prefixed, set in onReady(). No
store, no observable, no framework state layer. Cross-component state goes through the event bus
and through purpose-built shared modules — settings-bus.js,
vault-bus.js, poll.js.
Constants are frozen and centralised
shared/launch-defaults.jsverbatim · 02__javascript.md// ── launch-defaults.js — canonical launch constants ────────────────────────── //
// Single source of truth. Both sg-compute-compute-view and sg-compute-launch-form
// import from here. Update here only — do not duplicate locally.
export const REGIONS = Object.freeze([
'eu-west-2', 'us-east-1', 'ap-southeast-1', 'eu-west-1', 'us-west-2',
])Apache-2.0 — quoted from the estate's own source, not covered by this site's CC BY 4.0. Counted 2026-08-24.
Object.freeze on every exported constant, and a comment that names the consumers
and forbids local duplication. That is the same single source of truth instinct that
drives manifest.py and the repo-root version file, expressed with a
mechanism rather than a request. The pattern →
5. Why “no build step” is the interesting claim
It is worth arguing rather than just reporting, because it is a real trade with a real breaking point.
What it buys
- The source that runs is the source you read. No source maps, no build cache, no transpilation between what you see and what executes.
- No
node_modules, no lockfile, no bundler upgrade treadmill. - An immutable URL per version instead of a dependency resolver.
- A component is deployable by copying three files to a path.
- An agent reading the running page reads the actual code. Why that matters →
What it costs
- No TypeScript, no JSX, no compile-time checking of any kind.
- No tree-shaking and no minification.
- One network request per component file, and three files per component.
- No dependency resolution, so a dependency graph deeper than one level has nowhere to go.
- A CJS-only package cannot be used at all.
Where it stops working is the honest part: at the point you need a dependency graph deeper than one level, or a package that only ships as CommonJS. Neither has happened yet in 50 files, which is not the same as neither happening. Q7 →
6. What to fix before this becomes a standard
Four inconsistencies, in the order they should be settled. All four are cheap, and none of them can be settled by a site — they need a decision.
- Semicolons.
components/is semicolon-free;shared/is not. Pick one — the newer tree suggests dropping them — and write the config. - Two banner styles. JSDoc on components,
// ──on shared modules. Pick one per file type and say which. - The
sp-cli:event namespace is legacy naming in 23 files. Rename it with the rest, and note that it is a breaking change for external listeners. _privatein JavaScript versus Python rule 9. State that rule 9 is Python-only, or change one of the two. Right now it is neither enforced nor retired. Q2 →