mirror of
https://github.com/discourse/discourse.git
synced 2026-08-11 02:59:07 +08:00
The `dag-map` npm package (v2.0.2, last published 8 years ago) sorts by
walking from sink vertices and recursing over incoming edges. That makes
`before: X` land next to X for free, but an item declaring `after: X` is
only reached later through some unrelated sink, so it drifts away from
its anchor. On the post menu it drifts all the way to the end.
A button registered as `{ after: firstButtonKey }` renders last today:
```
dag-map: read like copyLink flag edit bookmark delete admin showMore reply plugin-button
this PR: read plugin-button like copyLink flag edit bookmark delete admin showMore reply
```
Three published plugins hit this, all asking to sit next to a core
button and all landing at the opposite end of the menu.
This replaces `dag-map` with a locality-preserving sort inlined into
`dag.ts`, the last remaining import of the package. Every vertex gets a
placement rank: a path of `(side, insertionIdx)` steps rooted at an
unanchored vertex. Kahn's algorithm then emits the lowest-ranked vertex
that is currently legal. Ranks only break ties among vertices whose
predecessors have all been placed, so the edges alone decide correctness
and the output is always a valid topological order. An anchored item
inherits its anchor's rank prefix, so registration order never outweighs
an anchor, and a key with a high in-degree no longer influences where
unrelated items land.
### Positions can be simpler
A quarter of the positioned `add()` calls across published plugins and
themes name both a `before` and an `after` anchor to pin an item into a
slot one anchor should have secured. Of those that can be tested against
their surface, every one works with a single anchor under this sort,
where only a third do under `dag-map`.
Arrays deserve a mention too: `before: [a, b]` means before *all* of the
listed keys, not whichever happens to exist. A defensive extra key can
drag an unrelated core item across the list, and dropping it to a single
anchor avoids that.
### Known limitation
When a customization claims the same anchor and the same side as a core
item, the core item keeps the adjacent slot, because core registers
first. Anchoring to that core item instead yields the intended
placement. This matches `dag-map`'s behaviour, so nothing regresses, and
it is pinned by a test.
### Typing and tests
The module is authored in TypeScript. `DAG` is generic over the value it
stores, with exported `DAGPosition`, `DAGOptions`, and
`DAGResolvedEntry` interfaces. `dag-test.ts` keeps the runtime behaviour
tests and adds coverage for hub-shaped graphs and for the before-side
guarantees, which the suite did not previously exercise. A compile-time
test at `type-tests/lib/dag-test.ts` asserts the generic contract with
expect-type.
Checked against every DAG `add()` call site in the published plugin and
theme corpus: no ordering regressions anywhere, and five customizations
get the placement they ask for and do not currently receive.
---------
Co-authored-by: David Taylor <david@taylorhq.com>
|
||
|---|---|---|
| .. | ||
| asset-processor | ||
| deprecation-silencer | ||
| discourse | ||
| discourse-i18n | ||
| discourse-markdown-it | ||
| discourse-types | ||
| pretty-text | ||
| .npmrc | ||
| polyfills.js | ||