feat(editor-icons): add the icon normaliser and package shell
The generator reads assets/*.svg and emits one module. Icons arrive from several sets with different viewboxes, stroke widths and fill conventions; normalising at build time means the overlay renders them all from a single code path.
This commit is contained in:
@@ -0,0 +1,692 @@
|
||||
---
|
||||
budget_kb: 340
|
||||
icons:
|
||||
# --- Disclosure, arrows, chrome -------------------------------------------
|
||||
chev-down: others/down-arrow-small.svg
|
||||
chev-right: others/right-arrow-small.svg
|
||||
chev-up: others/up-arrow.svg
|
||||
caret-down: others/dropdown-arrow.svg
|
||||
drag: others/drag.svg
|
||||
grab: others/grab.svg
|
||||
|
||||
# --- General verbs --------------------------------------------------------
|
||||
plus: general/add.svg
|
||||
minus: general/remove.svg
|
||||
close: general/x.svg
|
||||
check: general/check.svg
|
||||
check-bold: general/check-bold.svg
|
||||
search: general/search.svg
|
||||
more: general/more-actions.svg
|
||||
settings: general/setting.svg
|
||||
eye: appearance/show.svg
|
||||
eye-off: appearance/hide.svg
|
||||
list-view: general/show-as-list.svg
|
||||
grid-view: general/show-as-grid.svg
|
||||
question: others/question-mark.svg
|
||||
clipboard: others/clipboard.svg
|
||||
save: others/save.svg
|
||||
play: others/play.svg
|
||||
|
||||
# --- Alignment row (a design panel opens with exactly these) ---------------
|
||||
align-left: position/align-vertical-left.svg
|
||||
align-h-center: position/align-vertical-center.svg
|
||||
align-right: position/align-vertical-right.svg
|
||||
align-top: position/align-horizontal-top.svg
|
||||
align-v-center: position/align-horizontal-center.svg
|
||||
align-bottom: position/align-horizontal-bottom.svg
|
||||
distribute-h: position/distribute-horizontal-spacing.svg
|
||||
distribute-v: position/distribute-vertical-spacing.svg
|
||||
tidy-up: position/tidy-up.svg
|
||||
flip-h: alignment/flip-horizontal.svg
|
||||
flip-v: alignment/flip-vertical.svg
|
||||
rotation: alignment/rotation.svg
|
||||
|
||||
# --- Auto layout ----------------------------------------------------------
|
||||
al-horizontal: auto-layout/horizontal-layout.svg
|
||||
al-vertical: auto-layout/vertical-layout.svg
|
||||
al-wrap: auto-layout/wrap.svg
|
||||
al-add: auto-layout/use-auto-layout.svg
|
||||
al-remove: auto-layout/remove-auto-layout.svg
|
||||
al-baseline: auto-layout/icon-16-autolayout-alignment-baseline.svg
|
||||
al-absolute: auto-layout/absolute.svg
|
||||
gap-h: auto-layout/horizontal-gap-between-objects.svg
|
||||
gap-v: auto-layout/vertical-gap-between-objects.svg
|
||||
pad-h: auto-layout/horizontal-padding.svg
|
||||
pad-v: auto-layout/vertical-padding.svg
|
||||
pad-individual: auto-layout/individual-padding.svg
|
||||
pad-top: auto-layout/top-padding.svg
|
||||
pad-right: auto-layout/right-padding.svg
|
||||
pad-bottom: auto-layout/bottom-padding.svg
|
||||
pad-left: auto-layout/left-padding.svg
|
||||
|
||||
# --- Sizing (Hug / Fill / Fixed, plus min-max) ----------------------------
|
||||
size-hug: auto-layout/hug-content.svg
|
||||
size-fixed: auto-layout/fixed-width.svg
|
||||
size-fill: auto-layout/resize-to-fit.svg
|
||||
size-min-w: auto-layout/add-min-width.svg
|
||||
size-max-w: auto-layout/add-max-width.svg
|
||||
size-min-h: auto-layout/add-min-height.svg
|
||||
size-max-h: auto-layout/add-max-height.svg
|
||||
aspect-lock: auto-layout/lock-aspect-ratio.svg
|
||||
aspect-unlock: auto-layout/unlock-aspect-ratio.svg
|
||||
fit: auto-layout/resize-to-fit-1.svg
|
||||
|
||||
# --- Text -----------------------------------------------------------------
|
||||
text-size: text/text-font-size.svg
|
||||
text-line-height: text/text-line-height.svg
|
||||
text-letter-spacing: text/text-letter-spacing.svg
|
||||
text-paragraph-spacing: text/text-paragraph-spacing.svg
|
||||
text-indent: text/text-paragraph-indent.svg
|
||||
text-align-left: text/text-align-left.svg
|
||||
text-align-center: text/text-align-center.svg
|
||||
text-align-right: text/text-align-right.svg
|
||||
text-align-justify: text/text-align-justified.svg
|
||||
text-align-top: text/text-align-top.svg
|
||||
text-align-middle: text/text-align-middle.svg
|
||||
text-align-bottom: text/text-align-bottom.svg
|
||||
text-bold: text/bold.svg
|
||||
text-italic: text/italic.svg
|
||||
text-underline: text/underline.svg
|
||||
text-strike: text/strike-through-false.svg
|
||||
text-uppercase: text/uppercase.svg
|
||||
text-lowercase: text/lowercase.svg
|
||||
text-titlecase: text/title-case.svg
|
||||
text-truncate: text/truncate-text.svg
|
||||
text-list: text/number-list.svg
|
||||
text-resize-fixed: text/text-resize-fixed.svg
|
||||
text-resize-width: text/text-resize-width.svg
|
||||
text-resize-height: text/text-resize-height.svg
|
||||
|
||||
# --- Appearance & corners -------------------------------------------------
|
||||
opacity: appearance/opacity.svg
|
||||
blend-mode: appearance/apply-blend-mode.svg
|
||||
# The "Clip content" toggle. From Frame/, which is otherwise excluded —
|
||||
# the rest of that folder is boolean path operations a DOM editor cannot do.
|
||||
overflow-clip: frame/overflow-clip.svg
|
||||
overflow-visible: frame/overflow-display.svg
|
||||
corners-independent: appearance/independent-corners.svg
|
||||
corner-tl: appearance/top-left-corner-radius.svg
|
||||
corner-tr: appearance/top-right-corner-radius.svg
|
||||
corner-bl: appearance/bottom-left-corner-radius.svg
|
||||
corner-br: appearance/bottom-right-corner-radius.svg
|
||||
|
||||
# --- Fill -----------------------------------------------------------------
|
||||
fill-solid: fill/fill-solid.svg
|
||||
fill-gradient: fill/fill-gradient-linear.svg
|
||||
fill-image: fill/fill-image.svg
|
||||
fill-mixed: fill/fill-mixed.svg
|
||||
eyedropper: fill/eyedropper.svg
|
||||
gradient-linear: fill/gradient-linear.svg
|
||||
gradient-radial: fill/gradient-radial.svg
|
||||
gradient-angular: fill/gradient-angular.svg
|
||||
rotate-ccw: fill/rotate.svg
|
||||
|
||||
# --- Stroke ---------------------------------------------------------------
|
||||
stroke: stroke/border.svg
|
||||
stroke-width: stroke/stroke-width.svg
|
||||
stroke-custom: stroke/stroke-custom.svg
|
||||
border-top: stroke/border-top.svg
|
||||
border-right: stroke/border-right.svg
|
||||
border-bottom: stroke/border-bottom.svg
|
||||
border-left: stroke/border-left.svg
|
||||
border-none: stroke/border-none.svg
|
||||
stroke-solid: stroke/stroke-solid.svg
|
||||
stroke-dash: stroke/stroke-dash.svg
|
||||
|
||||
# --- Effects --------------------------------------------------------------
|
||||
effects: effects/effects.svg
|
||||
effect-drop-shadow: effects/effect-setting-drop-shadow.svg
|
||||
effect-inner-shadow: effects/effect-setting-inner-shadow.svg
|
||||
effect-layer-blur: effects/effect-setting-layer-blur.svg
|
||||
effect-bg-blur: effects/effect-setting-background-blur.svg
|
||||
blur: effects/blur.svg
|
||||
spread: effects/spread.svg
|
||||
|
||||
# --- Layout grid ----------------------------------------------------------
|
||||
grid: grid/grid.svg
|
||||
grid-columns: grid/layout-grid-columns.svg
|
||||
grid-rows: grid/layout-grid-rows.svg
|
||||
grid-uniform: grid/layout-grid-uniform.svg
|
||||
|
||||
# --- Constraints ----------------------------------------------------------
|
||||
constraints-h: constraints/constraints-horizontal.svg
|
||||
constraints-v: constraints/constraints-vertical.svg
|
||||
|
||||
# --- Layer kinds (the tree's per-node glyph) ------------------------------
|
||||
layer-frame: layers/frame.svg
|
||||
layer-group: layers/group.svg
|
||||
layer-text: layers/text.svg
|
||||
layer-component: layers/layer-component.svg
|
||||
layer-instance: layers/layer-instance.svg
|
||||
layer-vector: layers/layer-complex-vector.svg
|
||||
layer-section: layers/section.svg
|
||||
layer-slice: layers/slice.svg
|
||||
|
||||
# --- Toolbar --------------------------------------------------------------
|
||||
tool-move: toolbar/move.svg
|
||||
tool-hand: toolbar/hand-tool.svg
|
||||
tool-frame: toolbar/frame.svg
|
||||
tool-text: toolbar/edit-text.svg
|
||||
tool-inspect: toolbar/inspect.svg
|
||||
tool-measure: toolbar/measure.svg
|
||||
tool-comment: toolbar/comment.svg
|
||||
tool-scale: toolbar/scale.svg
|
||||
dev-brackets: toolbar/dev-brackets.svg
|
||||
keyboard: toolbar/keyboard-shortcuts.svg
|
||||
history: toolbar/version-history.svg
|
||||
crop: toolbar/crop.svg
|
||||
annotate: toolbar/annotate.svg
|
||||
actions: toolbar/actions.svg
|
||||
component: toolbar/component.svg
|
||||
image: toolbar/image.svg
|
||||
insert: toolbar/insert.svg
|
||||
|
||||
# --- Agent vocabulary -----------------------------------------------------
|
||||
# The chat timeline names one tool call per row. Every row used to lead with
|
||||
# the same status dot, so a Read and a Bash were distinguishable only by
|
||||
# reading their label. These are the glyphs that tell them apart — see
|
||||
# `overlay/src/chat/tool-row.ts`. The set has no "run a shell command" mark;
|
||||
# `command` (the ⌘ knot) is the nearest true thing in the set, and it reads
|
||||
# as "a command" rather than as a terminal, which is the right abstraction
|
||||
# for a row that might be `npm test` or `git status`.
|
||||
# The thinking row's glyph. It used to share the brand mark with the dock
|
||||
# head, which said "Airship" where it meant "the model is reflecting" — two
|
||||
# different statements on one shape. The set's own idea mark says the second.
|
||||
bulb-on: others/bulb-on.svg
|
||||
pencil: toolbar/pencil.svg
|
||||
doc-plus: toolbar/doc-plus.svg
|
||||
command: toolbar/command.svg
|
||||
# `Share/public.svg`, not `Others/language.svg`. The latter is the set's
|
||||
# translate control — a 文 beside an A — which on a WebFetch row reads as
|
||||
# "localise this", not "off the network".
|
||||
globe: share/public.svg
|
||||
# The Edit tab's own glyph, and the one exception to the `Icons/` exclusion
|
||||
# below: the `Icons/design.svg` mark. It first borrowed `settings` — a gear,
|
||||
# which in every editor means application preferences rather than "the visual
|
||||
# properties of the thing you selected" — then `Toolbar/edit object.svg`, a
|
||||
# bezier path with its handles out, which names *vector point editing*. That
|
||||
# is a thing this editor cannot do and the tab does not offer. The product
|
||||
# mark names the surface instead of a tool inside it, which is what a tab is.
|
||||
design: icons/design.svg
|
||||
|
||||
# --- Mode, files, versions ------------------------------------------------
|
||||
edit-mode: sidebar-left/edit.svg
|
||||
preview: sidebar-left/preview.svg
|
||||
present: sidebar-left/present.svg
|
||||
folder: sidebar-left/folder.svg
|
||||
libraries: sidebar-left/libraries.svg
|
||||
code: others/code.svg
|
||||
version-current: others/version-current.svg
|
||||
version-branch: others/version-new-branch.svg
|
||||
version-merged: others/version-merged.svg
|
||||
device-mobile: others/mobile.svg
|
||||
device-tablet: others/tablet.svg
|
||||
|
||||
# --- Style swatches (section headers) -------------------------------------
|
||||
style-fill: style/style-fill.svg
|
||||
style-text: style/style-text.svg
|
||||
style-effect: style/style-effect.svg
|
||||
style-grid: style/style-grid.svg
|
||||
# --- Shadow direction + spread (Effects). The offset presets the drop/inner shadow
|
||||
# rows offer, and the two blur ramps.
|
||||
# ----------------------------------------------------------------------
|
||||
shadow-top: effects/drop-shadow-top.svg
|
||||
shadow-bottom: effects/drop-shadow-bottom.svg
|
||||
shadow-left: effects/drop-shadow-left.svg
|
||||
shadow-right: effects/drop-shadow-right.svg
|
||||
shadow-top-left: effects/drop-shadow-top-left.svg
|
||||
shadow-top-right: effects/drop-shadow-top-right.svg
|
||||
shadow-bottom-left: effects/drop-shadow-bottom-left.svg
|
||||
shadow-bottom-right: effects/drop-shadow-bottom-right.svg
|
||||
shadow-spread: effects/drop-shadow-spread.svg
|
||||
shadow-min: effects/drop-shadow-min.svg
|
||||
shadow-mid: effects/drop-shadow-mid.svg
|
||||
shadow-max: effects/drop-shadow-max.svg
|
||||
inner-shadow-top: effects/inner-shadow-top.svg
|
||||
inner-shadow-bottom: effects/inner-shadow-bottom.svg
|
||||
inner-shadow-left: effects/inner-shadow-left.svg
|
||||
inner-shadow-right: effects/inner-shadow-right.svg
|
||||
inner-shadow-top-left: effects/inner-shadow-top-left.svg
|
||||
inner-shadow-top-right: effects/inner-shadow-top-right.svg
|
||||
inner-shadow-bottom-left: effects/inner-shadow-bottom-left.svg
|
||||
inner-shadow-bottom-right: effects/inner-shadow-bottom-right.svg
|
||||
inner-shadow-spread: effects/inner-shadow-spread.svg
|
||||
blur-small: effects/layer-blur-small.svg
|
||||
|
||||
# --- Border side combinations. `border-width` takes four values, and these are the
|
||||
# sixteen-way switch a design tool offers for picking which sides are on.
|
||||
# ----------------------------------------------------------------------
|
||||
border-top-bottom: stroke/border-top-bottom.svg
|
||||
border-left-right: stroke/border-left-right.svg
|
||||
border-top-left: stroke/border-top-left.svg
|
||||
border-top-right: stroke/border-top-right.svg
|
||||
border-bottom-left: stroke/border-bottom-left.svg
|
||||
border-bottom-right: stroke/border-bottom-right.svg
|
||||
border-top-left-right: stroke/border-top-left-right.svg
|
||||
border-bottom-left-right: stroke/border-bottom-left-right.svg
|
||||
border-top-left-bottom: stroke/border-top-left-bottom.svg
|
||||
border-top-right-bottom: stroke/border-top-right-bottom.svg
|
||||
|
||||
# --- Stroke caps, joins and markers — the SVG section's vocabulary.
|
||||
# ----------------------------------------------------------------------
|
||||
stroke-cap-none: stroke/stroke-cap-none.svg
|
||||
stroke-cap-round: stroke/stroke-cap-round.svg
|
||||
stroke-cap-square: stroke/stroke-cap-square.svg
|
||||
stroke-join-round: stroke/stroke-round.svg
|
||||
stroke-join-square: stroke/stroke-square.svg
|
||||
stroke-arrow-line: stroke/stroke-line-arrow.svg
|
||||
stroke-arrow-triangle: stroke/stroke-triangle-arrow.svg
|
||||
stroke-arrow-reversed: stroke/stroke-reversed-triangle.svg
|
||||
stroke-arrow-circle: stroke/stroke-circle-arrow.svg
|
||||
stroke-arrow-diamond: stroke/stroke-diamond-arrow.svg
|
||||
|
||||
# --- Text, second pass — the typographic controls the Text section grew.
|
||||
# ----------------------------------------------------------------------
|
||||
text-h1: text/h1.svg
|
||||
text-h2: text/h2.svg
|
||||
text-small-caps: text/small-caps.svg
|
||||
text-caps: text/text-caps.svg
|
||||
text-subscript: text/subscript.svg
|
||||
text-superscript: text/superscript.svg
|
||||
text-underline-dashed: text/underline-dashed.svg
|
||||
text-underline-dotted: text/underline-dotted.svg
|
||||
text-underline-wave: text/underline-wave.svg
|
||||
text-skip-ink: text/skip-ink-true.svg
|
||||
text-skip-ink-off: text/skip-ink-false.svg
|
||||
text-trim: text/leading-trim.svg
|
||||
text-trim-none: text/no-trim.svg
|
||||
text-code: text/code-snippet.svg
|
||||
text-code-block: text/code-block.svg
|
||||
text-num-normal: text/normal-number.svg
|
||||
text-num-mono-lower: text/monospace-lowercase-number.svg
|
||||
text-num-mono-upper: text/monospace-uppercase-number.svg
|
||||
text-num-prop-lower: text/proportional-lowercase-number.svg
|
||||
text-num-prop-upper: text/proportional-uppercase-number.svg
|
||||
text-style-replace: text/replace-text-style.svg
|
||||
text-style-update: text/update-text-style.svg
|
||||
text-resize-auto: text/text-resize-height-1.svg
|
||||
|
||||
# --- Design tokens / variables. The token badge and picker are the client.
|
||||
# ----------------------------------------------------------------------
|
||||
var-add: variable/add-variable.svg
|
||||
var-color: variable/variable-color.svg
|
||||
var-string: variable/variable-string.svg
|
||||
var-boolean: variable/variable-boolean.svg
|
||||
var-text: variable/variable-text.svg
|
||||
var-value: variable/variable-value.svg
|
||||
var-settings: variable/variable-setting.svg
|
||||
var-mode: prototype/set-variable-mode.svg
|
||||
var-apply: general/apply-variable.svg
|
||||
var-style-apply: general/apply-style-and-variable.svg
|
||||
var-info: others/variable-info.svg
|
||||
var-mapping: others/variable-mapping.svg
|
||||
|
||||
# --- Interaction triggers and actions. The forced-state row (:hover, :active,
|
||||
# :focus) and anything that later describes a transition reaches for these.
|
||||
# ----------------------------------------------------------------------
|
||||
proto-click: prototype/on-click.svg
|
||||
proto-drag: prototype/on-drag.svg
|
||||
proto-mouse-down: prototype/mouse-down.svg
|
||||
proto-mouse-up: prototype/mouse-up.svg
|
||||
proto-mouse-enter: prototype/mouse-enter.svg
|
||||
proto-mouse-leave: prototype/mouse-leave.svg
|
||||
proto-hover: prototype/while-hovering.svg
|
||||
proto-press: prototype/while-pressing.svg
|
||||
proto-delay: prototype/after-delay.svg
|
||||
proto-conditional: prototype/conditional.svg
|
||||
proto-none: prototype/none.svg
|
||||
proto-back: prototype/back.svg
|
||||
proto-navigate: prototype/navigate-to.svg
|
||||
proto-scroll-to: prototype/scroll-to.svg
|
||||
proto-open-link: prototype/open-link.svg
|
||||
proto-change-to: prototype/change-to.svg
|
||||
proto-open-overlay: prototype/open-overlay.svg
|
||||
proto-close-overlay: prototype/close-overlay.svg
|
||||
proto-swap-overlay: prototype/swap-overlay.svg
|
||||
proto-overlay-bottom: prototype/layout-overlay-bottom.svg
|
||||
proto-overlay-left: prototype/layout-overlay-left.svg
|
||||
proto-set-variable: prototype/set-variable.svg
|
||||
proto-time-set: prototype/action-time-set.svg
|
||||
proto-time-jump: prototype/action-time-jump.svg
|
||||
proto-gamepad: prototype/gamepad.svg
|
||||
|
||||
# --- Colour adjustments and blend modes (the picker's second row).
|
||||
# ----------------------------------------------------------------------
|
||||
blend-off: color-picker/blendmode.svg
|
||||
blend-on: color-picker/blendmode-active.svg
|
||||
gradient-diamond: color-picker/gradient-diamond.svg
|
||||
adjust-contrast: color-picker/contrast.svg
|
||||
adjust-exposure: color-picker/exposure.svg
|
||||
adjust-saturation: color-picker/saturation.svg
|
||||
adjust-temperature: color-picker/temperature.svg
|
||||
adjust-tint: color-picker/tint.svg
|
||||
adjust-highlights: color-picker/highlights.svg
|
||||
adjust-shadows: color-picker/shadows.svg
|
||||
|
||||
# --- Vector and shape tools — the SVG section edits these primitives.
|
||||
# ----------------------------------------------------------------------
|
||||
shape-rect: toolbar/rectangle.svg
|
||||
shape-ellipse: toolbar/ellipse.svg
|
||||
shape-line: toolbar/line.svg
|
||||
shape-arrow: toolbar/arrow.svg
|
||||
shape-polygon: toolbar/polygon.svg
|
||||
shape-star: toolbar/star.svg
|
||||
shape-pen: toolbar/pen.svg
|
||||
shape-bend: toolbar/vector-bend.svg
|
||||
shape-paint: toolbar/vector-paint.svg
|
||||
shapes: toolbar/shapes.svg
|
||||
count-polygon: frame/count-polygon.svg
|
||||
count-star: frame/count-star.svg
|
||||
angle: frame/angle.svg
|
||||
mask: frame/mask.svg
|
||||
flatten: frame/flatten-selection.svg
|
||||
bool-union: frame/boolean-union.svg
|
||||
bool-subtract: frame/boolean-subtract.svg
|
||||
bool-intersect: frame/boolean-intersect.svg
|
||||
bool-exclude: frame/boolean-exclude.svg
|
||||
|
||||
# --- Cross-axis alignment per layout mode (the set splits these by direction).
|
||||
# ----------------------------------------------------------------------
|
||||
layer-align-h-top: layers/horizontal-layout/align-top.svg
|
||||
layer-align-h-center: layers/horizontal-layout/align-center.svg
|
||||
layer-align-h-bottom: layers/horizontal-layout/align-bottom.svg
|
||||
layer-align-v-left: layers/vertical-layout/align-left.svg
|
||||
layer-align-v-center: layers/vertical-layout/align-center.svg
|
||||
layer-align-v-right: layers/vertical-layout/align-right.svg
|
||||
layer-align-wrap-left: layers/wrap-layout/align-left.svg
|
||||
layer-align-wrap-right: layers/wrap-layout/align-right.svg
|
||||
layer-wrap-center: layers/layer-wrap-align-center.svg
|
||||
grid-column: grid/column.svg
|
||||
grid-row: grid/row.svg
|
||||
|
||||
# --- Chat, comments and review.
|
||||
# ----------------------------------------------------------------------
|
||||
comment-message: comment/message.svg
|
||||
comment-resolve: comment/make-as-resolved.svg
|
||||
comment-mention: comment/mention.svg
|
||||
comment-emoji: comment/add-emoji.svg
|
||||
comment-image: comment/add-image.svg
|
||||
comment-new: toolbar/comment-new.svg
|
||||
thumb-up: others/thumb-up.svg
|
||||
thumb-down: others/thumb-down.svg
|
||||
heart: others/heart.svg
|
||||
bulb: others/bulb-on.svg
|
||||
bulb-off: others/bulb-off.svg
|
||||
|
||||
# --- General chrome, second pass.
|
||||
# ----------------------------------------------------------------------
|
||||
view: general/view.svg
|
||||
spotlight: general/spotlight.svg
|
||||
team-library: general/team-library.svg
|
||||
indeterminate: general/intermediate.svg
|
||||
select-using: general/select-item-using.svg
|
||||
ready-for-dev: general/mark-as-ready-for-dev.svg
|
||||
language: others/language.svg
|
||||
square: others/square.svg
|
||||
rectangle: others/rectangle.svg
|
||||
portrait: others/portrait.svg
|
||||
connection: others/creating-a-connection.svg
|
||||
person: share/person.svg
|
||||
people: share/people.svg
|
||||
company: share/company.svg
|
||||
key: share/key.svg
|
||||
attention: share/attention.svg
|
||||
public: share/public.svg
|
||||
edit: sidebar-left/edit.svg
|
||||
disconnect: sidebar-left/disconnect.svg
|
||||
minimize: sidebar-left/minimize-ui.svg
|
||||
missing-font: sidebar-left/missing-font.svg
|
||||
syncing: sidebar-left/saving-syncing.svg
|
||||
|
||||
# --- Local marks. Not from the imported set — see "Marks we draw ourselves".
|
||||
# ----------------------------------------------------------------------
|
||||
logo: local/logo.svg
|
||||
panel-left: local/panel-left.svg
|
||||
panel-right: local/panel-right.svg
|
||||
dot: local/dot.svg
|
||||
gutter: local/gutter.svg
|
||||
lock: local/lock.svg
|
||||
unlock: local/unlock.svg
|
||||
claude: local/claude.svg
|
||||
codex: local/codex.svg
|
||||
opencode: local/opencode.svg
|
||||
|
||||
---
|
||||
|
||||
# Airship editor icons
|
||||
|
||||
The overlay's icon set. This front-matter is the **canonical manifest**: it maps
|
||||
a stable, semantic slug to a file under `assets/ui/`. `scripts/gen.mjs` reads
|
||||
it and emits `src/generated/icons.ts`, exactly the way `@airship/editor-tokens`
|
||||
turns `EDITOR.md` into `src/generated/editor.ts`.
|
||||
|
||||
```
|
||||
edit ICONS.md → pnpm --filter @airship/editor-icons gen → src/generated/icons.ts
|
||||
```
|
||||
|
||||
Never edit the generated file.
|
||||
|
||||
## Naming
|
||||
|
||||
Slugs are **kebab-case and semantic**, not source-verbatim — `text-size`, not
|
||||
`text font size`. Where a bare noun would be ambiguous across panels, the slug
|
||||
carries its domain as a prefix:
|
||||
|
||||
| Prefix | Domain | Example |
|
||||
| --- | --- | --- |
|
||||
| `al-` | Auto layout | `al-horizontal`, `al-remove` |
|
||||
| `pad-` / `gap-` | Spacing controls | `pad-individual`, `gap-v` |
|
||||
| `size-` | Resizing modes | `size-hug`, `size-fill` |
|
||||
| `text-` | The Text section | `text-line-height` |
|
||||
| `corner-` / `border-` | Per-side/per-corner toggles | `corner-tl`, `border-none` |
|
||||
| `layer-` | Layer-kind glyphs in the tree | `layer-component` |
|
||||
| `tool-` | Toolbar tools | `tool-hand` |
|
||||
| `effect-` | Effect types | `effect-inner-shadow` |
|
||||
|
||||
Add an icon by adding a line here, not by importing an SVG at a call site. The
|
||||
budget check below is the only thing keeping the bundle honest, and it can only
|
||||
see what goes through the manifest.
|
||||
|
||||
## Budget
|
||||
|
||||
`budget_kb` caps the total normalised markup. The generator prints a per-category
|
||||
size report and **fails the build** when the manifest exceeds it. Measured
|
||||
figures for the full corpus, after normalisation:
|
||||
|
||||
| Set | Icons | Raw | Gzipped |
|
||||
| --- | --- | --- | --- |
|
||||
| This manifest | 348 | ~312 KB | ~87 KB |
|
||||
| Everything in `assets/` | 509 | ~468 KB | ~124 KB |
|
||||
|
||||
The manifest went from 173 slugs to 348 in one pass, and `budget_kb` from 160 to
|
||||
320 with it. That doubled cold-start icon cost by ~40 KB gzipped, and it was a
|
||||
deliberate trade: the sections added alongside it (filters, media, vector, scope,
|
||||
tokens, the gradient editor) were reaching for glyphs that did not exist, and the
|
||||
failure mode when a slug is missing is not a fallback — `resolve()` throws, or,
|
||||
worse, the name lands in a text slot and renders as literal characters.
|
||||
|
||||
320 → 340 was the second raise, when normalisation stopped exempting the 24×24
|
||||
majority and every glyph gained a refit `<g>`. That is +16.4 KB of markup but
|
||||
only **+1.8 KB gzipped** — the wrappers are near-identical strings, which is the
|
||||
case compression is best at, and compression is what these bytes actually ship
|
||||
under. The alternative was baking the transform into the path coordinates, which
|
||||
costs no bytes but makes the refit invisible and unauditable; the wrapper can be
|
||||
read, diffed, and re-measured, and the generator does re-measure it.
|
||||
|
||||
What is still excluded, and why: the application-specific glyphs with no meaning
|
||||
in a DOM editor — component libraries and their variants, plugin and widget
|
||||
marks, presentation and whiteboard tools, collaboration affordances, captioning,
|
||||
engineering-handoff states. That is roughly 120 more unique glyphs and about
|
||||
100 KB. They are one manifest line each if the product ever grows a use for them.
|
||||
|
||||
Raising `budget_kb` is a decision, not a formality — the overlay bundle is
|
||||
re-fetched on every page load (`no-store`, it changes with every rebuild), so
|
||||
icon bytes are cold-start bytes. They are affordable only because
|
||||
`serveAirshipAsset` compresses (`packages/server/src/proxy.ts`).
|
||||
|
||||
A single object literal is retained wholesale by esbuild, so the registry cannot
|
||||
be tree-shaken: `icon(name)` is called with computed names in the layers tree and
|
||||
in the descriptor tables, so it has to be eager. Curation plus the budget check
|
||||
is the control, not the bundler.
|
||||
|
||||
## What the generator normalises
|
||||
|
||||
1. **Every glyph is refit onto one 24×24 box at one optical size.** Each icon's
|
||||
artwork is measured, then wrapped in a `translate(…) scale(…)` that centres
|
||||
it at **12.8 units**. After this pass every icon reports `box: 24` and one
|
||||
`icon(name, "md")` means one optical size everywhere. A file with no viewBox
|
||||
is a hard failure, because there is nothing safe to assume.
|
||||
|
||||
This used to exempt anything already 24×24, on the reasoning that those
|
||||
glyphs are drawn on the pixel grid and rescaling would only blur them —
|
||||
the set insets its artwork heavily, the span is about 12.8 units, and that
|
||||
inset *is* the set's visual rhythm.
|
||||
|
||||
That is true of the corpus and false of its members. Measured properly the
|
||||
artwork spans **7 to 24 units**: median 13.0, p90 16.0. The exemption covered
|
||||
300 of 346 glyphs, so optical size was free-floating almost everywhere, and
|
||||
icons from opposite ends of that spread ended up side by side — the bottom
|
||||
bar had `rotation` at 7.0 next to `doc-plus` at 18.0, a 2.6× difference in
|
||||
painted ink at the same nominal size.
|
||||
|
||||
Two details the measurement has to get right, both of which the old
|
||||
coordinate-pair scan did not:
|
||||
|
||||
- **Curves contribute their extrema, not their control points**, and `H`/`V`
|
||||
take a single operand. 297 of the 346 glyphs use `H`/`V`; a pair-wise scan
|
||||
drops those operands, and `plus`'s `M6.5 11.5H17.5` measured 0×0 — which
|
||||
under a general refit would have divided by zero and erased the glyph.
|
||||
- **`<defs>`, `<clipPath>` and `<mask>` are excluded.** Their geometry is
|
||||
structural rather than painted: `toolbar/move.svg`'s mask is a full-bleed
|
||||
rect spanning 17 of its 24 units, which is *not* the size of the cursor it
|
||||
reveals.
|
||||
|
||||
Stroked glyphs are fitted to 12.8 of **painted** extent, not geometry — their
|
||||
centrelines land at `12.8 − STROKE_REF` so the stroke's outer edge finishes
|
||||
where a filled glyph's silhouette would. See normalisation step 3.
|
||||
|
||||
The generator re-measures its own output and fails the build if any glyph is
|
||||
more than 0.01 units off the reference. That check is the thing that was
|
||||
missing: the old exemption was never verified, so the drift was invisible.
|
||||
2. **Paint becomes `currentColor`.** `#1B1F24` (481 fills + 36 strokes),
|
||||
`#007BE5` and `black` are rewritten. `white` is left alone — those 18 are
|
||||
`<clipPath>`/`<mask>` rects that must stay opaque — as is the single
|
||||
`#F24822`, which is a semantic red.
|
||||
3. **Stroke weight is pinned.** 26 of the 346 glyphs are stroked and *none*
|
||||
declares a `stroke-width`, so they inherit 1 user unit — which the refit
|
||||
`<g>` then scales along with the geometry. Left alone that makes stroke
|
||||
weight a function of how large the source happened to be drawn: `chev-down`
|
||||
came out at 3.02 units against its neighbours' 1.0. The generator writes
|
||||
`stroke-width="STROKE_REF / scale"` on the group, which cancels the transform
|
||||
exactly, so every stroked glyph paints the same weight.
|
||||
4. **Paint tone is completed, never overwritten.** The two-tone is the most
|
||||
recognisable property of the set — the Constraints and Auto Layout glyphs
|
||||
draw their frame at `0.3` and the meaningful part at `0.9` — and flattening
|
||||
it collapses them into mush, so any opacity the source states is left exactly
|
||||
alone.
|
||||
|
||||
What the source *doesn't* state is filled in. The grammar was only applied
|
||||
to part of the corpus: 146 icons had at least one element with no opacity at
|
||||
all, painting at 100% beside neighbours at 90%. Those get `0.9`, the subject
|
||||
tone. It is applied per element rather than on the refit `<g>`, because
|
||||
inherited opacity multiplies with the child's own — a group at `0.9` would
|
||||
drag every `0.3` context path to `0.27` and silently re-tune the two-tone.
|
||||
|
||||
All 356 icons now carry an explicit tone; 44 keep a `0.3` context layer.
|
||||
5. **Element ids are namespaced** to `ap-<slug>-<id>`. Only 19 files carry ids,
|
||||
but two `search` icons on one page would otherwise share — and break — a clip
|
||||
path.
|
||||
6. **Coordinates round to 2 dp**, which takes the corpus from ~600 KB of path
|
||||
data to ~442 KB for no visible change.
|
||||
|
||||
## Marks we draw ourselves
|
||||
|
||||
Ten slugs under `assets/local/` are not from the imported set. They go through the same
|
||||
manifest and the same normalisation as everything else — which is the point.
|
||||
They previously lived as hand-authored strings in the overlay's `icons.ts`,
|
||||
inset "roughly 4..20" (a **16-unit** span) to match the set by eye. The set's
|
||||
actual reference is 12.8, so the brand mark, both panel toggles and all three
|
||||
agent logos rendered about 25% oversized, and there were two competing
|
||||
conventions with nothing reconciling them.
|
||||
|
||||
- **`logo`** — Airship's mark: an `A` cut from two slabs. It has been a
|
||||
four-point spark and then a flying saucer. The spark was the house style of
|
||||
every AI feature shipped since 2023, so the one mark meant to say *Airship*
|
||||
was the one saying the least; the saucer said the name, but a pictogram in a
|
||||
dock head is a small illustration wherever you put it, and it kept reading as
|
||||
a drawing rather than as an identity. A monogram is the thing that stays a
|
||||
monogram at 16px.
|
||||
|
||||
Two shapes. The main slab is the A's right leg carried up through a mitred
|
||||
apex; the second is the left leg, detached, its flat top landing exactly on
|
||||
the first slab's inner edge. The counter is therefore open at the foot — a
|
||||
`V` of negative space rather than a closed triangle — which is what keeps the
|
||||
mark from filling in at small sizes, where a closed counter of this
|
||||
proportion would be the first thing to clog.
|
||||
|
||||
Everything follows from two numbers: every edge that is not a foot or the
|
||||
leg's top runs at dx/dy = 0.58, and the horizontal bar width is 4.53. That one
|
||||
slope, mirrored about the centre, is what makes the apex symmetric and the two
|
||||
legs read as the same slab. Two consequences of it are worth not "tidying"
|
||||
away later: the apex sits at x=12, and the left leg's top-right corner also
|
||||
lands at x=12, directly beneath it. Neither was placed — both fall out of the
|
||||
slope, and they are why the mark feels centred despite being made of two
|
||||
shapes that are not.
|
||||
|
||||
Solid, where the imported set is outline, deliberately: it sits beside `plus`,
|
||||
`history` and `rotate-ccw` in the dock head and should be the heaviest thing
|
||||
in that row. It carries no two-tone. The set's 0.9/0.3 split marks subject
|
||||
against context, and a logo is all subject — dimming a limb of it would draw a
|
||||
distinction the mark does not have. One compound path under the default
|
||||
`nonzero` rule: the two subpaths wind the same way and meet at exactly one
|
||||
point, so nonzero unions them and the junction needs no hand-fitted geometry.
|
||||
- **`panel-left` / `panel-right`** — a framed rect with a gutter on the side its
|
||||
panel is on, so the glyph reads as the layout it toggles. The gutter is the
|
||||
0.9 tone, the frame the 0.3 — the same two-tone grammar the imported set uses.
|
||||
- **`dot`** — a solid dot for neutral / in-progress status.
|
||||
- **`gutter`** — the elbow rail for the chat timeline's result line, a drawn
|
||||
`⎿`. The character itself (U+23BF) is absent from the latin font subsets we
|
||||
self-host, so it would fall out to a system face with different metrics inside
|
||||
a gutter whose entire job is alignment, and tofu where no fallback has it.
|
||||
- **`lock` / `unlock`** — the layers tree's lock pair, filled.
|
||||
`share/lock.svg` and `share/lock-open.svg` are stroke-expansion exports: a
|
||||
`<mask>` holding the padlock silhouette, and a visible path that is only the
|
||||
~1px ring around it. The layers tree draws its glyphs at `xs` (16px), where
|
||||
that ring scales to two thirds of a pixel — it antialiases to a grey smudge,
|
||||
and sits next to the eye and the layer-kind glyphs, which are filled. These
|
||||
are the mask's own silhouette painted directly, with the shackle as an
|
||||
even-odd counter, so the pair matches the family it lives in.
|
||||
- **`claude` / `codex` / `opencode`** — third-party product marks, for the agent
|
||||
picker. Unlike the rest these are not ours to redraw; they name a backend in
|
||||
the UI. Path data from `@lobehub/icons-static-svg`; the marks remain the
|
||||
trademarks of Anthropic, OpenAI and the opencode project respectively.
|
||||
|
||||
## Sizing
|
||||
|
||||
Every glyph's artwork spans 12.8 of the 24 units, centred — so the visible mark
|
||||
runs x/y 5.6..18.4. A 24-box glyph rendered at 16 px therefore yields a ~8.5 px
|
||||
mark, which is why `icon()` defaults to **24**, not 16, and why the
|
||||
`--ap-icon-size-*` scale starts at 16 rather than 12. Do not pass raw pixel
|
||||
numbers at call sites; use the named sizes so the scale stays swappable.
|
||||
|
||||
Normalising by bounding box makes every glyph occupy the same *box*, which is
|
||||
not quite the same as the same *optical weight*: a solid disc filling 12.8 units
|
||||
reads heavier than a thin chevron spanning the same 12.8. Where that matters the
|
||||
fix belongs at the call site, as a deliberate smaller `size` — not as a
|
||||
hand-tuned exception inside the set, which is how the drift started.
|
||||
|
||||
## Deliberately excluded
|
||||
|
||||
`prototype/` (26 — no prototyping surface), `comment/` (7 — no comment backend),
|
||||
`variable/` (8), `frame/` boolean operations (a DOM editor cannot union paths),
|
||||
`icons/` product marks (3 of 4 — `design` is kept as the Edit tab's glyph; the
|
||||
other three name applications Airship is not), most of `share/`
|
||||
(`lock`, `unlock` and `globe` are the exceptions) and most of `sidebar-left/`.
|
||||
|
||||
`color-picker/` is excluded as a **duplicate**: its 21 files are byte-identical to
|
||||
`fill/`.
|
||||
|
||||
The 25 `constraints/constraints-N.svg` anchor-matrix variants are excluded on
|
||||
purpose. That widget composes procedurally from a frame plus an anchor, which is
|
||||
what Airship does — see the Constraints control. Inlining the matrix would cost
|
||||
~20 KB to hard-code every state of something better expressed as two booleans.
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@airship/editor-icons",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "node scripts/gen.mjs && tsup src/index.ts --format esm --dts --sourcemap --clean",
|
||||
"clean": "rm -rf dist .turbo",
|
||||
"dev": "node scripts/gen.mjs && tsup src/index.ts --format esm --dts --sourcemap --watch",
|
||||
"gen": "node scripts/gen.mjs",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"yaml": "^2.6.1"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
EDITOR_ICONS,
|
||||
type EditorIcon,
|
||||
type EditorIconName,
|
||||
} from "./generated/icons";
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "dist",
|
||||
"types": [],
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user