Repository navigation
feat(contracts): environment icon overrides carry emoji, monogram, and image #12675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4e90090
e14faae
45b6063
dcba757
910e5a3
3815e24
4d7eb76
e2cb09c
26a72c9
bce1653
63b7aa1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import * as Schema from "effect/Schema"; | ||
|
|
||
| import { TrimmedNonEmptyString } from "./baseSchemas.ts"; | ||
|
|
||
| /** | ||
| * Leaf schemas shared by every user-chosen icon (project overrides and | ||
| * environment overrides). The composite override shapes differ per owner and | ||
| * live beside the owner; only the pieces that must agree across them live here. | ||
| */ | ||
|
|
||
| export const IconColor = Schema.Literals([ | ||
| "gray", | ||
| "red", | ||
| "orange", | ||
| "amber", | ||
| "yellow", | ||
| "lime", | ||
| "green", | ||
| "emerald", | ||
| "teal", | ||
| "cyan", | ||
| "sky", | ||
| "blue", | ||
| "indigo", | ||
| "violet", | ||
| "purple", | ||
| "fuchsia", | ||
| "pink", | ||
| "rose", | ||
| ]); | ||
| export type IconColor = typeof IconColor.Type; | ||
|
|
||
| /** A Lucide icon id, or any curated id that follows the same kebab-case grammar. */ | ||
| export const LucideIconName = TrimmedNonEmptyString.check( | ||
| Schema.isMaxLength(64), | ||
| Schema.isPattern(/^[a-z0-9]+(?:-[a-z0-9]+)*$/), | ||
| ); | ||
| export type LucideIconName = typeof LucideIconName.Type; | ||
|
|
||
| export const IconEmoji = TrimmedNonEmptyString.check(Schema.isMaxLength(32)); | ||
| export type IconEmoji = typeof IconEmoji.Type; | ||
|
|
||
| // Grapheme-count validation belongs to the write boundary, not snapshot decoding. | ||
| export const MonogramText = TrimmedNonEmptyString.check( | ||
| Schema.isMaxLength(32), | ||
| Schema.isPattern(/^[\p{L}\p{N}][\p{L}\p{N}\p{M}\u200c\u200d]*$/u), | ||
| ); | ||
| export type MonogramText = typeof MonogramText.Type; | ||
|
|
||
| /** | ||
| * Whether `text` reads as at most two characters, the bound a monogram tile | ||
| * can hold. The count is code points after stripping the combining marks and | ||
| * joiners `MonogramText` admits, which matches grapheme counting for Latin, | ||
| * digits, and accents either precomposed or decomposed. A Devanagari conjunct | ||
| * or a decomposed Hangul syllable counts high, so those scripts get one | ||
| * cluster rather than two. | ||
| * | ||
| * `Intl.Segmenter` would be exact, and Hermes does not ship it. Reaching for | ||
| * it where it exists would accept on the server and on web what mobile | ||
| * refuses, and one bound every client computes the same way is worth more | ||
| * than the extra scripts. | ||
| */ | ||
| export function isMonogramLength(text: string): boolean { | ||
| return Array.from(text.replace(/[\p{M}\u200c\u200d]/gu, "")).length <= 2; | ||
| } | ||
|
|
||
| /** | ||
| * Encoded length budget for an inline raster icon. A 64 by 64 PNG with alpha | ||
| * is at most 16 KiB of pixels before compression, which base64 grows by a | ||
| * third; the cap leaves room for the container without admitting a photo. | ||
| */ | ||
| export const ICON_IMAGE_DATA_URL_MAX_LENGTH = 32_768; | ||
|
|
||
| /** | ||
| * A small raster icon carried inline. The prefix is pinned to PNG rather than | ||
| * any `data:image/`. On web that is what keeps an SVG, which can script, out | ||
| * of the `<img>`, because Blink picks the decoder from the declared type. Mobile's | ||
| * image library sniffs content instead, so there the guarantee is that neither | ||
| * renderer in use has a script engine, not the prefix. | ||
| * | ||
| * Nothing past the signature is read, so this says nothing about frame count. | ||
| * APNG carries the same signature and animates. | ||
| * | ||
| * The first pattern spells out whole base64 quartets rather than a run of | ||
| * characters and loose padding. That refuses the three in four truncations | ||
| * that stop mid-quartet. One that stops on a quartet boundary still decodes, | ||
| * to a PNG carrying a signature and no pixels, and reaches the renderer. | ||
| * | ||
| * The second is the PNG signature, which base64 fixes to `iVBORw0KGg` for any | ||
| * PNG whatever its ninth byte. Checking the encoded prefix costs nothing on a | ||
| * path that decodes settings for every connected client on every change, and it | ||
| * means the declared type is the writer's claim while this is the evidence. | ||
| */ | ||
| export const IconImageDataUrl = Schema.String.check( | ||
| Schema.isMaxLength(ICON_IMAGE_DATA_URL_MAX_LENGTH), | ||
| Schema.isPattern( | ||
| /^data:image\/png;base64,(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/, | ||
| ), | ||
| Schema.isPattern(/^data:image\/png;base64,iVBORw0KGg/), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium
🤖 Copy this AI Prompt to have your agent fix this:
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I'm unable to act on this request because you do not have permissions within this repository.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I'm unable to act on this request because you do not have permissions within this repository. |
||
| ); | ||
| export type IconImageDataUrl = typeof IconImageDataUrl.Type; | ||
Uh oh!
There was an error while loading. Please reload this page.