* feat(build): publish each page's markdown at /path.md as well as /path/index.md
Hugo writes the Markdown output format beside the HTML it belongs to, so a page
at /articles/foo/ is published at /articles/foo/index.md. That is correct and it
stays: it is what the llms.txt convention prescribes for directory-style URLs,
it is the form /llms.txt already advertises for 670 pages, and it is the target
of the <link rel="alternate" type="text/markdown"> we emit in every page head.
What it is not is guessable. Every other documentation site I compared exposes a
page's markdown by swapping the extension, so a client holding only a URL can
construct /articles/foo.md directly instead of first fetching the HTML to read
the alternate link:
/articles/immutable-data-structures.md 404 -> 200 text/markdown
/articles/immutable-data-structures/index.md 200 200 (unchanged)
So publish both. A post-build step copies every public/**/index.md to
public/**.md; the originals are untouched, so no published URL changes. Netlify
already serves .md as text/markdown, and the files are small: 7.6 MB against a
1.4 GB build, 0.5%.
Deliberately a build step rather than a Netlify redirect. A splat has to be
terminal, so /articles/*.md is not a pattern Netlify accepts, and a rule that
cannot be verified locally is worse than a copy that can.
Adds npm run md:test, which builds the site, runs the script over the output and
checks all 789 aliases exist and match their source byte for byte, that the
canonical index.md and index.html are untouched, that nothing is written outside
the publish directory, and that a second run overwrites nothing. Verified the
tests fail when the script is stubbed out.
The test deletes its build directory on exit. A build is ~1.4 GB, and a suite
that leaves temp directories behind fills a disk faster than anyone notices.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(md): cut the alias tests down, and keep master's npm scripts
Review: the tests were redundant. Two of the four asserted what cp does, and
all four needed a full ~1.4 GB Hugo build to say it. Replaced with two tests
over a synthetic directory, covering the only two things the script decides:
skip the site root, never overwrite an existing file. 87 lines to 39, and the
run drops from a full site build to 0.26s.
package.json: this branch predates the charts work, so its scripts block
replaced master's. Merging as-is would have silently deleted viz:test and
viz:charts. Now keeps both and adds md:test.
Also trims the script's comment header from 19 lines to 8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(md): drop the npm script, run the test file directly
package.json is the site's manifest; a runner alias for one shell script does
not belong in it. The test runs as:
node --test test/markdown/aliases.test.mjs
package.json is now untouched by this branch, which also removes the risk of
the merge dropping master's viz scripts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
7 charts across 5 posts, in 3 kinds: two-panel columns, grouped columns and faceted lines. Replaces 1,024 KB of raster screenshots with 71 KB of inline SVG, hover and keyboard focus included.
Charts are generated from CSVs and committed, so a changed number shows up as a diff in review.
Pages also build as Markdown, where the shortcode emits the source data as a table instead of the raw SVG.
Joint article with Matin Mahmood of hyper3labs on hyperbolic embeddings for hierarchical data, and what it takes to serve them from Qdrant.
Covers the Poincare ball, a controlled comparison on the Google Product Taxonomy, the d+2 lift failing inside HNSW, and the prefetch plus Formula Query rescore that fixes it. Two figures are live against the collection: the taxonomy viewer and the CLIP comparison.
* add an island shortcode
* improving island shortcode, add two new island visualisations for the multitenancy doc
* moved islands dir
* fix
* loader fix
* add interactive islands to the quantization docs
Replace the two static figures in manage-data/quantization with islands,
built the same way as the multitenancy ones:
- bit-depth: a float vector encoded at 1 / 1.5 / 2 bits per dimension next
to the distribution of its components, with the thresholds, buckets and
bit codes, storage bars, and hover/pin tracing from either side.
- asymmetric: binary stored vector x binary or 8-bit scalar query, with a
scatter of estimated vs exact per-dimension contribution per panel.
The island shortcode now includes a content hash in the resource name, so
Hugo's dev server serves an edited island instead of the cached first build.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Islands: shared design system and brand palette (#2742)
* islands: shared design system, brand palette, shorter captions
Address the design review on #2739:
- Tenants use the brand secondaries (blue, violet, teal, Qdrant red) instead
of success/warning/error, which read as status.
- One shared design system in islands.scss: `--qi-*` tokens (colors, border,
radius, mono font) and `.qi-*` classes for the controls row, chip/toggle,
status line, SVG frame and labels. Per-island CSS only styles what is
specific to that diagram, on top of the tokens.
- Geist Mono across controls and all diagram labels.
- Cards: 1px neutral border, 6px radius, no shadows. The bit-depth segmented
control and the promotion buttons become the same chip as the tenant chips.
- Asymmetric: negative sign is Qdrant red; bit-depth: bits are teal.
- Captions trimmed to one sentence; detail moved into the article text.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* islands: keep the original tenant colors
Tenants go back to their previous hues in both multitenancy islands. The
shared categorical palette stays available in islands.scss for islands
that want it, but nothing forces it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Islands: adopt the design-system palette, tighten multitenancy reservations
Swap every island color to the Qdrant design-system palette (Color.pdf):
- Tokens: neutrals from the Neutral ramp (800/600/50/200/400, dark
100/500/900/800/700); accent stays Amaranth 500.
- Categoricals: Neon Blue 500, Purple 500, Cyan 800 (+ Cyan 100 tint),
Amaranth 500 — replacing the old secondary blue/violet/teal.
- Tenant hues in both multitenancy islands: Neon Blue 500, Green 700,
Warning 800, Amaranth 500.
- bit-depth bucket ramp and its shade() endpoints follow the Neon Blue
ramp (200 -> 400 -> 800, C_HIGH 900).
Dark mode kept --bd-pos at the light-theme step, leaving it at 1.79:1
against the dark surface; it now lightens to Neon Blue 600 (2.82:1), the
mirror of the existing light-theme override.
Both multitenancy islands also relied on the default ratio="16 / 9",
which is the fallback PNG's aspect, not the island's — reserving 70-134px
of dead height between the diagram and its caption. Set the ratios to the
islands' measured aspects (12/5 and 15/7), as the quantization islands
already do.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Islands: make every quantization fill a real palette colour
Both quantization diagrams encoded magnitude continuously, which put most
cells on colours that are not in the design system:
- bit-depth's shade() lerped between two palette endpoints, so a float
cell landed on e.g. rgb(145,141,213) — a point 35% along Neon Blue
200 -> 900, and not a palette entry.
- asymmetric's cells carried a fractional fill-opacity, which composites
against the page background and lands off-palette the same way.
Both now step through discrete palette entries instead. bit-depth gets a
five-step Neon Blue value ramp (--bd-shade-1..5, 200/300/500/700/900,
preserving the old endpoints); asymmetric gets four-step per-sign ramps
(--aq-pos-1..4 Neon Blue, --aq-neg-1..4 Amaranth) that top out at the
brand 500 a fully saturated cell already used. Dark theme runs both the
other way so a near-zero cell stays quiet against the dark surface.
var() is not valid in an SVG presentation attribute, so the fills move to
inline style. The histogram bands lose their 0.9 opacity for the same
reason.
Verified by walking every rendered SVG node's computed fill and stroke in
both themes: no opaque paint outside the palette remains. Alpha-based
effects that are not colour choices stay: the cell hairline
(--qi-cell-stroke, neutral at 30%/18%), the scatter error whiskers (0.5)
and the promotion placeholder label (0.7).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Islands: add annotated palette preview screenshots
Light and dark renders of all four islands at 1200px, each diagram
followed by a strip naming every colour it paints — role, design-system
ramp and step, and hex.
The labels are generated, not written by hand: the preview page paints
each CSS custom property onto a probe element, reads the computed colour
back, and looks the hex up in a map parsed from the design system's
own colour sheet. A chip only renders a name if the pixel matches a real
palette entry, so these double as a record that nothing off-palette
survives in either theme.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Remove accidental island palette preview PNGs from repo root.
---------
Co-authored-by: generall <andrey@vasnetsov.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: qdrant-cloud-bot <111755117+qdrant-cloud-bot@users.noreply.github.com>
qdrant-landing/static/ is copied verbatim by Hugo, so a full-resolution
camera JPEG is downloaded by every reader of the post. 178MB of such files
were added in the last 12 months and are still shipping; .git is now 2.4GB.
Checks only the files the PR touches, so it costs nothing on other PRs.
Escape hatch: the 'allow-large-images' label skips the job.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five of these were 7008x4672 straight off the camera, 20-25MB each. They are
embedded inline in the blog post and qdrant-landing/static/ is served verbatim,
so every reader of that post downloaded 110MB of photos.
Capped at 2000px / q82, which is well above any display size on the site:
121MB -> 11MB for the directory, no visible difference.
mogrify -auto-orient -resize '2000x2000>' -quality 82 -strip *.jpg
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>