Skip to content

Releases: spekhq/spek

v1.19.1

Choose a tag to compare

@kewang kewang released this 08 Oct 05:10

Security: spek's servers answer only spek's own pages. (#69)

  • Web: the API server is local-only. While npm run dev ran, the API server listened on every network interface and let any web page read its responses, so another device on the same network, or any page open in your browser, could list directories on your machine and read OpenSpec content. It now listens on 127.0.0.1 only and refuses any request that is not from the app itself: another site, another port on localhost, or a host name pointed at your machine (DNS rebinding). The dev server on 5173 is loopback-only as well
  • IntelliJ: the plugin's endpoints refuse other local pages. The IDE already refused other sites, but a page served on another port of your machine could read the plugin's API, because the IDE's built-in server allows any local origin. Only the plugin's own pages (the tool window and the external-browser view) are served now
  • Behaviour change: the web app can no longer be opened from another machine (for example a phone on your LAN). npm run dev now stops with an error when port 5173 is taken, instead of moving to another port. One dev server can browse every worktree, so a second one is not needed

v1.19.0

Choose a tag to compare

@kewang kewang released this 06 Oct 08:03

Highlight: specs grouped into folders are visible. OpenSpec accepts a spec.md at any depth under openspec/specs/ — specs/contracts/pagination/spec.md is a valid capability — but spek only looked one level down, so a repository that groups its capabilities into folders showed nothing for them. Thanks to @flatrick (Patrik) (#61)

  • Specs are found at any depth, in the main specs tree and in every change's delta specs, by OpenSpec's own rule: dot-entries are skipped, symlinked folders are not followed, and a linked spec.md counts only if it points inside the specs tree. A spec's name is its full path, e.g. contracts/pagination
  • The Specs page is a folder tree with a filter, and the VS Code sidebar and IntelliJ tool window nest the same way. In VS Code, a spec that also has specs under it lists them before its own headings
  • The graph labels a nested spec by its last segment, with the full path as its tooltip, and two specs with the same last segment open their own pages
  • Security: a spec read can no longer reach outside the specs a scan lists. The web server built the file path straight from the spec or change name in the URL, so a name carrying ../ (also as an encoded %2F) could read a file outside the repository. Every spec read, on every surface, now checks the name and serves only a spec the list would show
  • Behaviour change: a symlinked folder under specs/ is no longer listed, and neither is a spec.md symlink pointing outside it, matching what OpenSpec itself validates and archives
  • Internal: @spekjs/core 1.14.0 adds the @spekjs/core/spec-topic subpath; @spekjs/ui 1.4.0 requires it

v1.18.1

Choose a tag to compare

@kewang kewang released this 25 Sep 03:12

Three fixes, two of them to the GitHub Action.

  • VS Code in a browser no longer opens a broken tab on every in-app click (#59). In code-server, GitHub Codespaces and vscode.dev, clicking the sidebar's Overview / Specs / Changes, a change card or a spec link also opened a new browser tab at a 404 address, on top of the navigation that did happen inside the panel. VS Code forwards every link click in a webview to the workbench to open, even one the page already handled; desktop VS Code silently refuses those addresses, which is why the bug never showed there. In-app links now stay in the panel, external links still open as before, and the spec page's table of contents scrolls smoothly instead of jumping. Thanks to @Philogag for reporting
  • The generated HTML snapshot survives content that looks like markup (#54). An artifact containing </script> ended the page's data early, so the viewer never started and the rest of the data showed up as page text; an artifact containing <!-- followed by <script> stopped the viewer from starting too. Both built without an error. The embedded data and the page title are now escaped, and the build fails, naming the part, if the page it assembled would not read back as written. This affects the GitHub Action's output and the live demo. Thanks to @pierreboissinot (Pierre Boissinot) for reporting and proposing the fix
  • The GitHub Action treats its inputs as text, never as shell commands (#56). repo-path, output-path and title were pasted into the action's shell scripts, so a title such as My "draft" specs broke the build's arguments, and a workflow passing in an outside value (a PR title, a branch name) could have that value run as a command. They are now passed as data, and an output-path containing a newline is refused. Behaviour change: a value that relied on the shell expanding it, such as output-path: $HOME/x.html, now arrives literally; ${{ }} expressions in your own workflow are unaffected. Users of spekhq/spek@v1 receive this with this release

v1.18.0

Choose a tag to compare

@kewang kewang released this 17 Sep 07:09

Highlight: a keyword's casing is read the way OpenSpec reads it. spek marked one casing rule across every keyword it highlights, and the keywords do not carry the same obligation. A scenario body is free text that no version of OpenSpec parses, so uppercase WHEN / THEN is a template convention — a spec written with **Given** / **When** / **Then** is perfectly valid and rendered with no highlighting at all. SHALL / MUST is the opposite: OpenSpec matches it case-sensitively and reports a requirement carrying no uppercase one. The rule is now decided per keyword group, against what OpenSpec actually does with each.

  • Title-case Gherkin steps are highlighted (#53). Given, When, Then and And are marked when the whole emphasised run is the keyword — **Given** a project is registered — which is how such specs are written. Uppercase is unchanged
  • Ordinary prose is not marked. A requirement beginning "When the server receives a request, it SHALL respond" marks only SHALL, as before. Recognising a step by its position instead was measured across 300 specs from 242 repositories and rejected: it marks that sentence, which every repository has, to reach the 2% that write title case
  • MUST / SHALL and the four delta operations stay uppercase-only. Red means normative here and lowercase "must" is an ordinary verb; **Modified**: heads an impact list in many proposals and names no delta operation there
  • No keyword is highlighted inside a heading. An emphasised one used to be — ## **ADDED** Requirements showed a badge while the unemphasised form every spec actually uses did not
  • A requirement or scenario heading drops its keyword whatever the casing. ### requirement: Foo displays as Foo, in the rendered content and in every table of contents, as ### Requirement: Foo already did. OpenSpec's own parser accepts the variant, so leaving the keyword visible showed formatting noise in place of the heading's name
  • Internal: @spekjs/core 1.13.0 changes specHeadingLabel's behaviour on a heading whose keyword differs only in case

v1.17.0

Choose a tag to compare

@kewang kewang released this 11 Sep 07:48

Highlight: search finds what is actually there. spek implemented search four times — the web server, the VS Code host, the IntelliJ server and the static build — and the four answered the same query differently. The web index scored an exact match out of the results once it sat more than about 40 characters into a file, so search was effectively blind past the first line of anything; the VS Code host carried a copy of the same defect. The rule is now stated once, and every surface runs it.

  • A term that appears verbatim is found, wherever it sits in the file (#51). The fuzzy index ranked a match by its distance from the start of the document and discarded anything further in. Typo tolerance is gone with it: matching is now exact and case-insensitive, which is what the other two surfaces already did
  • Task text is searchable in the static build (#52). The embedded tasks artifact carried only the parsed checklist, so every word the Tasks tab displays was invisible to search on the GitHub Action's output and the live demo
  • A change is found by its own name — in the form the result card shows it, so a query copied off a result finds that result. Previously the web and VS Code surfaces matched file content only
  • One result per spec and per change, taken from the file that actually contains the query. A term in three of a change's files used to list it three times, and a name match used to hand back a snippet with nothing the reader typed in it
  • Each result says which artifact answered and marks archived changes, so a row explains itself even when there is nothing to highlight in it
  • Results are ordered the same way everywhere, with the most recently archived change first rather than the oldest
  • A malformed search request is answered the same way by both servers. IntelliJ treated a missing query as an empty one; a repeated q crashed the web route
  • Internal: @spekjs/core 1.12.0 exposes the rule on a browser-safe @spekjs/core/search subpath, with a shared fixture corpus holding the TypeScript and Kotlin implementations in agreement

v1.16.0

Choose a tag to compare

@kewang kewang released this 27 Aug 02:50

Highlight: a change's non-Markdown artifacts are visible. A schema sets each artifact's filename through generates:, and not every artifact is Markdown — event-driven requires asyncapi.yaml. spek discovered only root *.md and the specs/ tree, so such a change rendered every tab except the one its schema asks for.

  • Root .yaml / .yml / .json files are artifacts, shown as syntax-highlighted text. The tab is named for the file's stem with a format badge, using the full filename only when a Markdown artifact shares it. Thanks to @nthansen (Norman Hansen) (#50)
  • They sort into their schema position, count in the change-list badge, are reachable by search, and refresh live on edit — on every surface
  • Discovery stays root-only and skips dotfiles, so .openspec.yaml and subdirectory files never become tabs
  • Code fences in proposal.md / design.md are highlighted too. A fence with no language stays plain
  • Internal: @spekjs/core 1.11.0 adds the "data" kind and the shared file listing behind count, search and discovery

v1.15.0

Choose a tag to compare

@kewang kewang released this 19 Aug 05:32

Highlight: the schema workflow diagram now says which steps come after implementation — and its lines can finally be seen. OpenSpec has no way to declare "this artifact is produced once the change is implemented": an artifact's requires may name only other artifacts, so authors point such a step at the last planning artifact and state the real ordering in prose. spek rendered the declared graph faithfully, which put those steps on apply's own level, reading as its peers — in superpowers-bridge that placed verify beside apply, inviting exactly the mistake the schema's own runtime precheck exists to block.

  • A step that follows implementation is drawn after apply, on a dashed edge captioned as spek's inference. The ordering is derived from the requires graph — an artifact outside everything apply needs, whose own dependencies cover all of them — and the edge is never drawn like a declared one, because openspec status does not block on it. Of the 88 OpenSpec schemas discoverable on GitHub, 18 declare such a step; the rule is a no-op on every built-in schema, and about 82% precise across everything it flags, which is why it announces itself rather than asserting an ordering. Thanks to @nthansen (Norman Hansen) (#48)
  • apply is a real node in the flow now, so the steps after it hang off it and archive follows the tail rather than sitting beside it. A schema that declares an artifact literally named apply — superspec does, as an implementation receipt — no longer collapses into the apply phase: both are separate, selectable steps drawn in the schema's own vocabulary
  • A derived edge is explained once, and never erases a dependency the CLI enforces. Where a declared path already implies the ordering, the derived edge is dropped instead of repeating the explanation on every step below it; where the reverse held, an inference used to imply a real requires away, leaving a node whose only incoming line said "openspec does not block on this"
  • An edge no longer detours through the node it exists to go around. Two edges converging on one step were ordered by the other end's column alone, so a tie put the long way round on the inside slot — and with the curve forced there it swung wide enough to be drawn straight through apply
  • Every line in the diagram is visible in both themes. Edges, arrowheads, the archive step's dashed outline and both legend swatches were drawn in the panel-hairline colour — 1.22:1 dark and 1.13:1 light, at full strength, so no opacity of it could have helped. In this diagram a line is not decoration: an arrow is the only thing stating that specs depends on proposal, and a dash is the only non-colour cue separating declared from derived from not declared by this schema. They now measure 5.52:1 and 5.17:1
  • A selected step's generates label is readable. Over the accent wash a selected step draws, it measured 4.45:1 in the light theme — under the floor, in the ordinary case of selecting a step that produces a file
  • A graph node's label stays readable whichever node is drawn next to it. The halo behind each label was painted inside its own node's group, so it survived a collision with the nodes drawn before it and was painted over by the ones drawn after — the protection held in one direction only, at no cost to any colour, which is why nothing measurable saw it
  • An OpenSpec installation that cannot answer the artifact-order query stops costing a process start per change. A refusal about one change is still never held against the schema every other change shares, but it is now remembered against the change it was about — so an openspec too old for status --change --json is asked once per change instead of on every open and every watcher-driven refresh
  • IntelliJ treats an unreadable CLI response as a failure, not as an answer. An exit-0 run whose output could not be parsed was cached as "this schema has no order" for the full cache window
  • Internal: @spekjs/core 1.10.0 exposes the post-implementation derivation and the levelling behind it; @spekjs/ui 1.3.1 corrects the colour-contract documentation that shipped with 1.3.0

v1.14.0

Choose a tag to compare

@kewang kewang released this 14 Aug 10:20

Highlight: the light theme is readable. It was never opt-in — prefers-color-scheme on the web, VS Code's theme, IntelliJ's — so it is what a reader gets rather than a mode they chose, and nearly every colour in it failed WCAG AA. An error message measured 2.76:1; the spec diff's added lines 1.70:1, sitting directly beside removed lines that were merely bad. The dark theme was audited on the same terms rather than assumed sound, and carried two failures of its own.

  • Error, success and warning colours are now defined per theme. Each was one Tailwind shade applied to both, and no 400 shade in any family reaches even 3:1 on a light background — the spec diff's added and removed lines, every page's error message, the repo picker's detection states and the jj conflict badge all failed there
  • Secondary text is readable in both themes. Timestamps, counts, empty states and the labels beside them measured 2.34:1 light and 3.54:1 dark
  • Links, the active sidebar item and search highlighting take a deeper amber in the light theme. The previous one was 3.04:1 as plain text and 3.50:1 where a search hit sits on a tint of it — that tint, not the link, is what set the new value
  • A completed task no longer fades its own links and code spans. The row carried 60% opacity, which composites everything beneath it: its body text measured 3.24:1 dark and 2.77:1 light, and no colour could have compensated because the fading happens after the colour is chosen. Completion is marked by colour now, with the strikethrough and checkmark unchanged
  • The task progress bar's complete state is distinguishable from its track — 2.02:1 before, in the light theme
  • The graph and the timeline follow the theme. Node fills, legend swatches and the archived timeline bars were hard-coded colours no theme could reach: the graph's spec nodes measured 1.85:1 on a light page, and its archived nodes 2.93:1 in the dark one, because that colour was a copy of a token that had since been corrected. Edges were drawn in the panel-border colour, which is 1.22:1 at full strength and cannot be seen at any opacity
  • Graph labels stay readable where they overlap a node, and the timeline's "today" marker and both bar states are drawn at full strength instead of faded
  • A CLI failure is no longer remembered for the full cache window. An unreachable openspec binary meant 30 seconds of "unavailable" even after PATH was fixed; a failure that resolves is now retried on the next read, while one the installed CLI reproduces identically is still cached (#46)
  • Building from source works on Windows. @spekjs/core and @spekjs/ui used Unix-only rm -rf / cp in their build scripts, so npm run build failed under cmd.exe. Thanks to @nthansen (Norman Hansen) (#47)

v1.13.1

Choose a tag to compare

@kewang kewang released this 11 Aug 11:07

Highlight: the rule beside an open section now starts and ends where its content does. 1.13.0 gave each requirement its own rule and a gap between them, but the rule was drawn down the section's box — and a box holds two spaces its content does not. Reported from the IntelliJ tool window (issue #42), one round after the change that introduced the gap.

  • The rule starts at its heading, not 20px above it. That space is also what separates a section from the one before it, so of the 28px between two requirements, 20px was drawn as rule — 1.13.0's gap was real but invisible, and a page of requirements still read as one interrupted line with a notch in it
  • And it ends at the last of its content, instead of running past it. The trailing space below a section's last paragraph sits inside the box too, so the rule overshot the thing it was marking by a further 20px — plainest under the last scenario of a requirement
  • An open requirement's heading no longer sits 1px right of a closed one's. The rule used to be a border, which inset everything inside an open section; drawn as its own element it does not. Headings and disclosure arrows now line up down the page regardless of open state
  • The rule stays visible in Windows high contrast, where the previous drawing method would have been discarded
  • A scenario written with no requirement above it now takes a top-level section's spacing, 4px lower than before: how much room a section leaves above its heading follows its nesting, not its heading level

v1.13.0

Choose a tag to compare

@kewang kewang released this 11 Aug 03:48

Highlight: a requirement is now called what it is named. Every requirement heading in a spec opened with the word Requirement: and every scenario with Scenario: — the same twelve characters at the front of every heading in the document, taken from exactly where a reader scans for what distinguishes one from the next. Suggested from the IntelliJ tool window (issue #42), where a few hundred pixels of width makes the cost plainest.

  • Requirement and scenario headings drop their format keyword, in the rendered spec, in both tables of contents, and in the VS Code sidebar's spec tree — so the surfaces agree on what a heading is called. This became removable only in 1.12.0: before the ADDED Requirements label was restored to visibility, the keyword was the only thing naming what these sections were. Presentation only — your files, every heading id and every deep link are untouched, and the VS Code tree keeps the authored text in its tooltip
  • The rule beside an open section now marks that section. Consecutive requirements drew rules that met, forming a single unbroken line down the whole page: a bracket around everything says the same as no bracket at all. Each requirement now carries its own, ending where the requirement ends. A nested open scenario no longer draws a second rule of equal weight beside its parent's — the doubling read as one ornament repeated rather than as two levels
  • Expanding a scenario no longer nudges the rest of the page. The gap between sections depended on what the last visible element inside one happened to be, so opening a scenario pushed every requirement below it down by a further 8px
  • A little more room around the fold — the body sits further from the rule, and the disclosure arrow is no longer drawn against it
  • Internal: @spekjs/core 1.9.0 adds specHeadingLabel, the single rule behind the heading text every surface displays