* 🐛 Fix DTCG token import discriminator and group-level $type inheritance
Closes#8342.
The DTCG Community Group Final Report (W3C, 2025-10-28) specifies:
"The presence of a $value property definitively identifies an object
as a token."
"A token's type can be specified by the optional $type property [...]
Furthermore, the $type property on a group applies to all tokens
nested within that group."
Two bugs in `common/src/app/common/types/tokens_lib.cljc` violate the
spec and silently break import of any third-party DTCG file that uses
group-level type inheritance:
1. `flatten-nested-tokens-json` used `(not (contains? v "$type"))` as
the group-vs-token discriminator. A group node carrying only a
`$type` (to set a default for child tokens) was misidentified as a
token, then immediately discarded because it had no `$value`.
2. `schema:dtcg-node` declared both `$type` and `$value` as required,
so even after the discriminator was fixed any leaf token that
relied on group-level type inheritance failed `dtcg-node?`
validation and never reached the parser.
The combined effect: importing a spec-compliant DTCG file that
expressed types at the group level produced a TokensLib with no
tokens at all, because every leaf was discarded as "unknown type".
Penpot-exported files were unaffected because Penpot always emits
both `$type` and `$value` on every token and never attaches `$type`
to a group, so the existing tests covered only the inline-type
shape.
- `schema:dtcg-node`: mark `$type` optional.
- `flatten-nested-tokens-json`: use `$value` as the discriminator
(anything without `$value` is a group), accept an optional
`inherited-type` accumulator that carries the nearest enclosing
group `$type` down through recursion, and resolve a token's type
from its own `$type` first, falling back to the inherited type.
A token's own `$type` always wins over the inherited one (per
spec).
Added `parse-dtcg-group-type-inheritance` covering both cases:
- group `$type` is inherited by tokens that don't declare their own
(`colors.red`, `colors.blue`, `space.small`)
- token `$type` overrides the inherited group `$type`
(`colors.danger`, `space.large`)
Existing DTCG round-trip tests continue to pass because they all
declare `$type` at the token level, which the new code still honours.
CHANGES.md entry added under the 2.17.0 Bugs-fixed section.
* 📚 Do not update CHANGES.md
We are changing the procedures to not update the changelog on each PR. Instead, we use github tracking to check what issues come in a release, and update the changelog automatically in a batch.
Signed-off-by: Andrés Moya <hirunatan@hammo.org>
---------
Signed-off-by: Andrés Moya <hirunatan@hammo.org>
Co-authored-by: MilosM348 <milos.milic001@outlook.com>
* ✨ Add search bar to prototype interaction destination dropdown
On pages with many boards the destination dropdown becomes hard to
navigate. Add an optional `searchable?` flag to the shared select
component that renders a case-insensitive filter input at the top of
the dropdown, and opt it in for the interaction destination select.
- Filtering reuses the already-computed option list (no extra queries).
- Arrow-key navigation tracks the filtered list.
- Search clears automatically when the dropdown closes, so reopening
starts with the full list.
- New `labels.no-matches` i18n key renders when nothing matches.
Closes#8618
Signed-off-by: moorsecopers99 <patellscott18@gmail.com>
* ✨ Use trigger input as search field in destination dropdown
Per review on #9006, the searchable select now uses the visible trigger
input as the filter field itself rather than an extra sticky input
inside the dropdown. The trigger behaves like a filterable select:
typing filters the options without permitting free-text values.
Signed-off-by: moorsecopers99 <vadanamihai409@gmail.com>
* ♻️ Update css on old select component
---------
Signed-off-by: moorsecopers99 <patellscott18@gmail.com>
Signed-off-by: moorsecopers99 <vadanamihai409@gmail.com>
Co-authored-by: moorsecopers99 <patellscott18@gmail.com>
Co-authored-by: moorsecopers99 <vadanamihai409@gmail.com>
Co-authored-by: Alejandro Alonso <alejandro.alonso@kaleidos.net>
* 🐛 Fix library updates reappear after file is reloaded
Summary
Migrate synced_at timestamps to a standalone file_library_sync table to ensure sync state is tracked for both direct and transitive libraries.
Problem
Transitive libraries (libraries imported by other libraries) are not stored as direct rows in file_library_rel. Because the system previously coupled synced_at directly to the file_library_rel schema, transitive libraries lacked a persistent location for their sync timestamps. This caused sync states to be lost or incorrectly reported for nested dependencies.
Changes
Schema Migration: Created file_library_sync and migrated existing synced_at values from file_library_rel.
Decoupling: Removed tight Foreign Key coupling to allow sync rows to exist independently of specific relationship records.
Persistent Writes: Added upsert-file-library-sync! helper. Updated all import, duplication, and RPC write paths (v1/v2/v3 importers, link-file-library) to ensure every write persists a sync row.
Unified Reads: Updated both direct and recursive/transitive library queries to fetch synced_at from the new table.
Testing: Added regression tests to verify that sync rows are correctly created/updated even when a transitive relation is absent in file_library_rel.
Impact
This fix ensures that the system accurately records and retrieves sync states for the entire library dependency tree, resolving the bug where nested libraries appeared out of sync.
* ✨ MR review