Skip to content

Control station/export frontend kit - #669

Draft
JavierRibaldelRio wants to merge 12 commits into
developfrom
control-station/export-frontend-kit
Draft

JavierRibaldelRio wants to merge 12 commits into
developfrom
control-station/export-frontend-kit

Conversation

@JavierRibaldelRio

Copy link
Copy Markdown
Member

No description provided.

globals.css declared @source globs reaching into testing-view and
competition-view by relative path. Those paths resolve through the
@workspace/ui symlink and every app importing globals.css inherited
both views' classes, whether or not it was one of them.

Each app now declares its own @source glob, matching the pattern
already used by adj-view, logging-view and competition-view. Adds the
missing one to testing-view and drops the per-app globs plus a dead
commented-out line from the kit.

testing-view CSS drops from 127.93 kB to 117.15 kB (148 selectors).
Verified every removed class is unused in testing-view and the kit,
and that all five apps still build.
The frontend-kit packages are about to emit dist/ directories. Without
this, every git status would list them as untracked.
The socket layer hardcoded ws://127.0.0.1:${port}/backend and the set
of logger modules was a closed union of this repo's view names. Neither
works for a frontend living outside this repository.

- Add SocketConfig { url, host, port, path, secure, heartbeatIntervalMs }.
  connect() still accepts a bare port, and with no config at all it
  resolves to ws://127.0.0.1:4000/backend, so the five apps behave
  exactly as before. Adds configure()/configureSocket() to set the
  defaults once at boot, and resolveSocketUrl() plus a url getter for
  diagnostics.
- Fix a dead reconnect: connect() returns early while a socket is open
  and cleanup() was private, so useWebSocket's reconnect did nothing
  whenever it was actually needed. Adds a public disconnect() and a
  reconnect() that closes before reopening.
- Widen LoggerModule to accept any string while keeping autocomplete
  for the known ones, export createLogger, add registerLoggerColor and
  an ansi map, and ship colors for all five views. Existing logger.*
  call sites are untouched.
- Honour heartbeatIntervalMs, where 0 disables the heartbeat.
- Export loggerColors from the package index, which it never was.
40 imports inside the kit referenced the package by its own name
(@workspace/ui/lib/utils and friends). Self-reference resolution is
supported unevenly across bundlers and, once the kit is published, it
risks a consumer ending up with two instances of cn. One file already
used a relative path, so this makes the kit internally consistent.

Also drops the now-dead paths block from the kit's tsconfig, so a
mistyped import fails loudly instead of resolving through the alias.

All five apps build and their CSS output hashes are unchanged.
…ckage

A new frontend will live outside this repository and needs the kit. That
requires publishing it, and publishing requires a real npm scope:
@workspace is the Turborepo template placeholder, not an organisation.

- Merge @workspace/core into @workspace/ui as src/core/, exposed as the
  ./core subpath. One package means one version, one build and no
  internal workspace: dependency to coordinate when publishing. The
  React-free boundary the kit README describes survives as a folder.
- Rename the directory ui -> kit, since it no longer holds only UI, and
  fix the esling-config -> eslint-config typo.
- Rename @workspace/* to @hyperloop-upv/* across 183 files.
- Drop the kit alias from the five app tsconfigs. Those paths pointed
  into the kit's source tree by relative path, which is exactly what
  does not exist for an external consumer. They were already inert:
  they live in a solution-style config with files: [], and the
  tsconfig.app.json that actually compiles src has no paths at all, so
  the apps already resolve the kit through its exports map.

eslint-config and typescript-config stay private: publishing the former
would force its 13 plugins into consumers' production dependencies.

All five apps build with byte-identical CSS output, lint reports no new
problems, and both .claude worktrees are untouched.
Publishing needs compiled output: an external consumer gets .tsx inside
node_modules, and @vitejs/plugin-react-swc does not transform files
there. Builds with Vite in library mode using Rollup's preserveModules,
which mirrors src/ into dist/ so the wildcard subpath exports keep
resolving as components are added, with no entry list to maintain.

Chose Rollup over tsc and tsup because neither rewrites relative
specifiers: both would emit extensionless imports, which is invalid ESM
and only works because Vite retries extensions. Verified the output
imports ../../lib/utils.js, with the extension.

- exports still point at src/, so the five apps keep today's HMR
  straight into kit source. publishConfig.exports points at dist/ and
  is what a consumer sees. This requires pnpm publish: npm ignores the
  override and would ship a package pointing at .tsx.
- Ship src/ alongside dist/ so the published globals.css keeps its own
  @source glob working against the package's own sources. No build-time
  CSS transform and no generated second stylesheet.
- react, react-dom, lucide-react, tailwindcss and tw-animate-css become
  peers. Two copies of React break hooks, and LucideIcon leaks into the
  public props of SegmentedControl.
- Drop the preinstall guard, which npm and yarn would run from inside a
  dependency and abort the consumer's install on.
- Drop next-themes, zod and zustand: unused since the store was emptied.
- Re-inject the 18 "use client" directives, which Rollup strips from
  the emitted JS.
- Enforce the React-free boundary of src/core with a lint rule instead
  of only documenting it in the README.

All five apps build with unchanged CSS output; tarball asserted for
private, preinstall, exports retargeting, peers and workspace: leakage.
The workspace tables still listed ui and core as two packages. Rewrites
the kit README around the published package: where code goes and why
src/core may not import React, the build and why Rollup rather than tsc
or tsup, why the tarball ships src/, and a consumer recipe covering
peers, index.css, the vite dedupe, the tsconfig, pointing the socket at
a remote backend and registering a logger module.

States that the registry is not set up yet and that a tarball from
pnpm pack is the way to consume it meanwhile.
The install step passed --filter=ui --filter=core. pnpm filters by
package name, and those were never the names: the packages were
@workspace/ui and @workspace/core, now merged into
@hyperloop-upv/frontend-kit. Both filters selected nothing.

Uses --filter="./frontend/**" instead, matching the build and test
steps in the same job, so the install cannot drift from the package
names again.
Adds the registry scope mapping and a manual publish workflow, so the
kit can reach a frontend living in another repository.

The root .npmrc carries no auth token on purpose. Every @hyperloop-upv/*
dependency here is a workspace link, so pnpm never contacts the
registry and nobody needs a token to install; a committed
_authToken=${NODE_AUTH_TOKEN} would break pnpm install for anyone
without that variable. Verified that a frozen-lockfile install still
succeeds with no token present.

The workflow is workflow_dispatch only and defaults to dry-run, so its
first ever run cannot publish by accident, and the publish step is
gated on an explicit boolean false so it fails closed. Before the
irreversible step it checks the dispatched version against the
committed one and asserts the tarball: private, the preinstall hook,
publishConfig.exports actually being applied, the ./core subpath, the
peers, a leaked workspace: range, and that src/ ships so Tailwind can
scan the kit inside node_modules.

Publishing uses pnpm, not npm: npm ignores publishConfig.exports and
would ship a package whose exports point at .tsx, with no error.

Both scripted steps were extracted from the YAML and run locally
against the real tarball.
The README still said the registry was not set up. Documents the
consumer .npmrc, the classic PAT with read:packages that GitHub
Packages requires even to download a public package, the CI
permissions, and the release steps with the warning that a published
version can never be reused.
Every Control Station release now publishes the kit at the same
version, so v11.3.2 of the station and
@hyperloop-upv/frontend-kit@11.3.2 refer to the same commit. The
publish workflow gains a workflow_call trigger and release.yaml calls
it as its last job, gated on the whole release having succeeded:
publishing cannot be undone and must not happen for a release that
failed to build. A publish-kit input, on by default, opts out.

The version is injected in CI with pnpm version --no-git-tag-version
rather than read from the committed package.json, which is the
convention electron-app already follows; the kit's version becomes the
same 0.0.0 placeholder, and the git tag is the record of what shipped.

Prereleases get the next dist-tag instead of latest. The repo has
shipped 11.3.2-beta-2 and 11.3.2-1, and publishing either as latest
would make a prerelease the default install for every consumer.

The dist-tag derivation and the version guard were both extracted from
the YAML and run locally across the version formats this repo uses,
including a leading v and the empty case.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant