From fab9ea4e761bbd15eb1aafab2dd422abe9294227 Mon Sep 17 00:00:00 2001 From: sheehanmunim Date: Wed, 19 Aug 2026 03:16:48 -0400 Subject: [PATCH] feat: pick your own sidebar artwork, and wear the MT brand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Artwork upstream is a side effect of running a Dev or Nightly build. MT Code ships one release, so artwork becomes a choice: - Appearance → Sidebar artwork picks "Match the build" (upstream's behaviour), None, Night sky, Blueprint, or one of your own images. - Your own artwork lives in server settings, so it follows the account to every client attached to it - desktop, browser, phone - instead of sitting in one device's local storage. Deleting one leaves the header bare rather than quietly falling back to a scene nobody picked. Brand, while here: MT Model wears the Munim mark instead of a generic sparkle, the Chrome extension is "MT Code Desktop Control" with MT icons, notification copy names the running app, and the hosted build stamps the same single version the installers do instead of the checked-in package version. Builds on upstream's artwork toggle (pingdotgg/t3code#4652). Co-Authored-By: Claude Opus 5 (1M context) --- apps/web/src/components/Icons.tsx | 21 +-- .../components/SidebarStageBackdrop.test.tsx | 64 +++++++-- .../src/components/SidebarStageBackdrop.tsx | 79 ++++++++++- .../settings/DesktopNotificationsSettings.tsx | 8 +- .../components/settings/SettingsPanels.tsx | 126 ++++++++++++++++++ .../src/components/settings/settingsSearch.ts | 5 + native/t3-chrome-extension/background.js | 6 +- native/t3-chrome-extension/icons/icon-128.png | Bin 9197 -> 17031 bytes native/t3-chrome-extension/icons/icon-16.png | Bin 673 -> 893 bytes native/t3-chrome-extension/icons/icon-32.png | Bin 1584 -> 2204 bytes native/t3-chrome-extension/icons/icon-48.png | Bin 2585 -> 3984 bytes native/t3-chrome-extension/install.ps1 | 2 +- native/t3-chrome-extension/install.sh | 2 +- native/t3-chrome-extension/manifest.json | 20 ++- packages/contracts/src/settings.ts | 43 ++++++ scripts/personal-publish-web.sh | 16 ++- 16 files changed, 340 insertions(+), 52 deletions(-) diff --git a/apps/web/src/components/Icons.tsx b/apps/web/src/components/Icons.tsx index fc5a37fc67fb..59a91c618d31 100644 --- a/apps/web/src/components/Icons.tsx +++ b/apps/web/src/components/Icons.tsx @@ -700,25 +700,16 @@ export const ACPRegistryIcon: Icon = ({ className, ...props }) => ( export const MtModelIcon: Icon = ({ className, ...props }) => ( + {/* The Munim mark, same path the app icon draws. */} - - ); diff --git a/apps/web/src/components/SidebarStageBackdrop.test.tsx b/apps/web/src/components/SidebarStageBackdrop.test.tsx index eca741af8c89..4feb4b2f21c7 100644 --- a/apps/web/src/components/SidebarStageBackdrop.test.tsx +++ b/apps/web/src/components/SidebarStageBackdrop.test.tsx @@ -2,7 +2,10 @@ import { describe, expect, it } from "vite-plus/test"; import { renderToStaticMarkup } from "react-dom/server"; import { + DEV_BACKDROP, + NIGHTLY_BACKDROP, resolveEnvironmentIdentificationPillLabel, + resolveSidebarArtwork, resolveSidebarStageBackdropVariant, resolveSidebarStageFocusRingOffsetClass, StageBackdropArt, @@ -11,8 +14,8 @@ import { describe("SidebarStageBackdrop", () => { it("resolves stage artwork only when enabled", () => { - expect(resolveSidebarStageBackdropVariant("Dev")).toBe("dev"); - expect(resolveSidebarStageBackdropVariant("Nightly")).toBe("nightly"); + expect(resolveSidebarStageBackdropVariant("Dev")).toEqual(DEV_BACKDROP); + expect(resolveSidebarStageBackdropVariant("Nightly")).toEqual(NIGHTLY_BACKDROP); expect(resolveSidebarStageBackdropVariant("Dev", false)).toBeNull(); expect(resolveSidebarStageBackdropVariant("Alpha")).toBeNull(); }); @@ -25,16 +28,16 @@ describe("SidebarStageBackdrop", () => { }); it("matches the focus-ring offset to each artwork palette", () => { - expect(resolveSidebarStageFocusRingOffsetClass("nightly")).toBe( + expect(resolveSidebarStageFocusRingOffsetClass(NIGHTLY_BACKDROP)).toBe( "focus-visible:ring-offset-(--stage-night-bottom)", ); - expect(resolveSidebarStageFocusRingOffsetClass("dev")).toBe( + expect(resolveSidebarStageFocusRingOffsetClass(DEV_BACKDROP)).toBe( "focus-visible:ring-offset-(--stage-art-bottom)", ); }); - it.each(["nightly", "dev"] as const)( - "uses unique SVG definition ids when %s artwork is rendered more than once", + it.each([NIGHTLY_BACKDROP, DEV_BACKDROP] as const)( + "uses unique SVG definition ids when $kind artwork is rendered more than once", (variant) => { const markup = renderToStaticMarkup( <> @@ -50,8 +53,8 @@ describe("SidebarStageBackdrop", () => { ); it("paints each artwork variant with theme-owned color tokens", () => { - const nightlyMarkup = renderToStaticMarkup(); - const devMarkup = renderToStaticMarkup(); + const nightlyMarkup = renderToStaticMarkup(); + const devMarkup = renderToStaticMarkup(); expect(nightlyMarkup).toContain("var(--stage-night-bottom)"); expect(nightlyMarkup).toContain("var(--stage-night-line)"); @@ -62,12 +65,49 @@ describe("SidebarStageBackdrop", () => { }); it.each([ - ["nightly", "96 0 8192 96"], - ["dev", "64 0 8192 96"], - ] as const)("uses the compact %s crop inside the send button", (variant, viewBox) => { + [NIGHTLY_BACKDROP, "96 0 8192 96"], + [DEV_BACKDROP, "64 0 8192 96"], + ] as const)("uses the compact crop inside the send button", (variant, viewBox) => { const markup = renderToStaticMarkup(); expect(markup).toContain(`viewBox="${viewBox}"`); - expect(markup).toContain(`stage-${variant === "dev" ? "blueprint" : "nightly"}`); + expect(markup).toContain(`stage-${variant.kind === "dev" ? "blueprint" : "nightly"}`); + }); +}); + +describe("resolveSidebarArtwork", () => { + const custom = [{ id: "art_1", name: "Skyline", image: "data:image/png;base64,AAAA" }]; + + it("defers to the build channel only for auto", () => { + expect(resolveSidebarArtwork({ selection: "auto", stageLabel: "Nightly", custom })).toEqual( + NIGHTLY_BACKDROP, + ); + // A release build has no channel artwork, which is why picking one matters. + expect(resolveSidebarArtwork({ selection: "auto", stageLabel: "", custom })).toBeNull(); + }); + + it("honours an explicit pick regardless of channel", () => { + expect(resolveSidebarArtwork({ selection: "night", stageLabel: "", custom })).toEqual( + NIGHTLY_BACKDROP, + ); + expect(resolveSidebarArtwork({ selection: "day", stageLabel: "Nightly", custom })).toEqual( + DEV_BACKDROP, + ); + expect(resolveSidebarArtwork({ selection: "none", stageLabel: "Nightly", custom })).toBeNull(); + }); + + it("renders the account's own artwork, and nothing once it is deleted", () => { + expect(resolveSidebarArtwork({ selection: "art_1", stageLabel: "", custom })).toEqual({ + kind: "custom", + image: custom[0]!.image, + name: custom[0]!.name, + }); + expect(resolveSidebarArtwork({ selection: "art_1", stageLabel: "", custom: [] })).toBeNull(); + }); + + it("stays out of the way when artwork is switched off entirely", () => { + expect( + resolveSidebarArtwork({ selection: "night", stageLabel: "", custom, enabled: false }), + ).toBeNull(); }); }); diff --git a/apps/web/src/components/SidebarStageBackdrop.tsx b/apps/web/src/components/SidebarStageBackdrop.tsx index 772017a6d086..d45a77884bd2 100644 --- a/apps/web/src/components/SidebarStageBackdrop.tsx +++ b/apps/web/src/components/SidebarStageBackdrop.tsx @@ -4,10 +4,44 @@ import { useId } from "react"; import { APP_HAS_UPDATE_TRACKS, APP_STAGE_LABEL } from "../branding"; import { resolveServerBackedAppStageLabel } from "../branding.logic"; import { primaryServerConfigAtom } from "../state/server"; +import { usePrimarySettings } from "../hooks/useSettings"; -export type SidebarStageBackdropVariant = "nightly" | "dev"; +export type SidebarStageBackdropVariant = + | { readonly kind: "nightly" } + | { readonly kind: "dev" } + | { readonly kind: "custom"; readonly image: string; readonly name: string }; export type EnvironmentIdentificationPillLabel = "Dev" | "Nightly"; +export const NIGHTLY_BACKDROP: SidebarStageBackdropVariant = { kind: "nightly" }; +export const DEV_BACKDROP: SidebarStageBackdropVariant = { kind: "dev" }; + +/** + * Resolve the artwork to draw from the account's choice. + * + * "auto" defers to the build channel the way upstream does; every other value + * is a deliberate pick and ignores the channel entirely, which is the point on + * a single-release build like MT Code. + */ +export function resolveSidebarArtwork(input: { + readonly selection: string; + readonly stageLabel: string; + readonly custom: ReadonlyArray<{ readonly id: string; readonly name: string; readonly image: string }>; + readonly enabled?: boolean; +}): SidebarStageBackdropVariant | null { + if (input.enabled === false) return null; + const selection = input.selection.trim(); + if (selection === "none") return null; + if (selection === "night") return NIGHTLY_BACKDROP; + if (selection === "day") return DEV_BACKDROP; + if (selection !== "auto" && selection.length > 0) { + const artwork = input.custom.find((entry) => entry.id === selection); + // A deleted artwork leaves the header bare rather than silently falling + // back to a scene the user did not choose. + return artwork ? { kind: "custom", image: artwork.image, name: artwork.name } : null; + } + return resolveSidebarStageBackdropVariant(input.stageLabel); +} + // A wide viewBox keeps the 96-unit art height at a fixed scale while sidebar resizing reveals // more horizontal canvas instead of zooming the scene. const STAGE_BACKDROP_VIEW_BOX = "0 0 8192 96"; @@ -18,15 +52,15 @@ export function resolveSidebarStageBackdropVariant( ): SidebarStageBackdropVariant | null { if (!enabled) return null; const normalized = stageLabel.trim().toLowerCase(); - if (normalized === "nightly") return "nightly"; - if (normalized === "dev") return "dev"; + if (normalized === "nightly") return NIGHTLY_BACKDROP; + if (normalized === "dev") return DEV_BACKDROP; return null; } export function resolveSidebarStageFocusRingOffsetClass( variant: SidebarStageBackdropVariant, ): string { - return variant === "nightly" + return variant.kind === "nightly" ? "focus-visible:ring-offset-(--stage-night-bottom)" : "focus-visible:ring-offset-(--stage-art-bottom)"; } @@ -52,7 +86,17 @@ export function useEnvironmentStageLabel(): string { } export function useSidebarStageBackdropVariant(enabled = true): SidebarStageBackdropVariant | null { - return resolveSidebarStageBackdropVariant(useEnvironmentStageLabel(), enabled); + const stageLabel = useEnvironmentStageLabel(); + const artwork = usePrimarySettings((settings) => ({ + selection: settings.sidebarArtwork, + custom: settings.customSidebarArtworks, + })); + return resolveSidebarArtwork({ + selection: artwork.selection, + stageLabel, + custom: artwork.custom, + enabled, + }); } /** Stage-channel header art; palettes mirror the per-channel app icons in `assets/`. */ @@ -68,11 +112,32 @@ export function SidebarStageBackdrop({ variant }: { variant: SidebarStageBackdro } export function StageBackdropArt({ variant }: { variant: SidebarStageBackdropVariant }) { - return variant === "nightly" ? : ; + if (variant.kind === "custom") return ; + return variant.kind === "nightly" ? : ; } export function StageBackdropButtonArt({ variant }: { variant: SidebarStageBackdropVariant }) { - return variant === "nightly" ? : ; + if (variant.kind === "custom") return ; + return variant.kind === "nightly" ? : ; +} + +/** + * A user's own artwork. Drawn as a cover-cropped band so any aspect ratio + * fills the header the way the built-in scenes do. + */ +function CustomArt({ + variant, +}: { + variant: Extract; +}) { + return ( +
+ ); } const NIGHTLY_STARS: ReadonlyArray<{ diff --git a/apps/web/src/components/settings/DesktopNotificationsSettings.tsx b/apps/web/src/components/settings/DesktopNotificationsSettings.tsx index dd3ff01d60ca..f159afcaf2cf 100644 --- a/apps/web/src/components/settings/DesktopNotificationsSettings.tsx +++ b/apps/web/src/components/settings/DesktopNotificationsSettings.tsx @@ -16,6 +16,8 @@ import { toastManager } from "../ui/toast.tsx"; import { SettingsRow, SettingsSection } from "./settingsLayout.tsx"; import { searchableSetting } from "./settingsSearch.ts"; +import { APP_DISPLAY_NAME } from "~/branding"; + const EVENT_OPTIONS: ReadonlyArray<{ readonly event: DesktopNotificationEvent; readonly title: string; @@ -158,14 +160,14 @@ function useDesktopNotificationSettingsModel() { }; const masterDescription = isElectron - ? "Notify me when T3 Code is in the background." + ? `Notify me when ${APP_DISPLAY_NAME} is in the background.` : browserPermission.permission === "denied" ? "Blocked in your browser settings." : browserPermission.permission === "unsupported" ? "Not supported by this browser." : browserPermission.permission === "default" - ? "Allow notifications when T3 Code is in the background." - : "Notify me when T3 Code is in the background."; + ? `Allow notifications when ${APP_DISPLAY_NAME} is in the background.` + : `Notify me when ${APP_DISPLAY_NAME} is in the background.`; return { settings, diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 38f700e23b55..282ab27156fb 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -19,6 +19,8 @@ import { } from "@t3tools/client-runtime/state/runtime"; import { DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE, + DEFAULT_SIDEBAR_ARTWORK_SELECTION, + MAX_CUSTOM_SIDEBAR_ARTWORK_BYTES, DEFAULT_UNIFIED_SETTINGS, type EnvironmentIdentificationMode, MAX_CODE_FONT_SIZE, @@ -1154,6 +1156,8 @@ export function AppearanceSettingsPanel() { } /> ) : null} + + @@ -1444,6 +1448,128 @@ const ADVANCED_TYPOGRAPHY_TARGET_IDS: ReadonlySet = new Set([ * and a settings-search jump to an override row flips Advanced on so the * target exists to scroll to. */ +/** + * Sidebar artwork: pick a built-in scene, one of your own, or nothing. + * + * Upstream only draws artwork on Dev/Nightly builds. MT Code ships a single + * release, so the artwork is a preference; custom pieces live in server + * settings, which is what makes them follow the account to every client + * attached to it rather than sitting in one browser's local storage. + */ +function SidebarArtworkRow() { + const settings = usePrimarySettings(); + const updateSettings = useUpdatePrimarySettings(); + const fileInputRef = useRef(null); + const [addError, setAddError] = useState(null); + const custom = settings.customSidebarArtworks; + const selection = settings.sidebarArtwork; + + const options = useMemo( + () => [ + { value: "auto", label: "Match the build" }, + { value: "none", label: "None" }, + { value: "night", label: "Night sky" }, + { value: "day", label: "Blueprint" }, + ...custom.map((artwork) => ({ value: artwork.id, label: artwork.name })), + ], + [custom], + ); + + const onPickFile = async (file: File) => { + setAddError(null); + if (file.size > MAX_CUSTOM_SIDEBAR_ARTWORK_BYTES * 0.7) { + // base64 inflates by ~4/3, so the on-disk limit is hit before the file one. + setAddError("That image is too large. Pick one under about 350 KB."); + return; + } + const image = await new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = () => resolve(typeof reader.result === "string" ? reader.result : null); + reader.onerror = () => resolve(null); + reader.readAsDataURL(file); + }); + if (image === null || !image.startsWith("data:image/")) { + setAddError("That file could not be read as an image."); + return; + } + const id = `art_${Math.random().toString(36).slice(2, 10)}`; + const name = file.name.replace(/\.[^.]+$/, "").slice(0, 60) || "Artwork"; + updateSettings({ + customSidebarArtworks: [...custom, { id, name, image, createdAt: new Date().toISOString() }], + sidebarArtwork: id, + }); + }; + + const removeArtwork = (id: string) => { + updateSettings({ + customSidebarArtworks: custom.filter((artwork) => artwork.id !== id), + ...(selection === id ? { sidebarArtwork: "auto" } : {}), + }); + }; + + return ( + updateSettings({ sidebarArtwork: DEFAULT_SIDEBAR_ARTWORK_SELECTION })} + /> + ) : null + } + control={ +
+ +
+ {custom.some((artwork) => artwork.id === selection) ? ( + + ) : null} + +
+ { + const file = event.target.files?.[0]; + event.target.value = ""; + if (file) void onPickFile(file); + }} + /> + {addError !== null ? ( + {addError} + ) : null} +
+ } + /> + ); +} + function TypographySection() { const [advanced, setAdvanced] = useLocalStorage( TYPOGRAPHY_ADVANCED_STORAGE_KEY, diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index 64b6bf4027fc..3377aa73e1b2 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -212,6 +212,11 @@ export const SETTINGS_SEARCH_ITEMS = [ title: "Workspace tabs", to: "/settings/general", }, + { + id: "sidebar-artwork", + title: "Sidebar artwork", + to: "/settings/appearance", + }, { id: "keybindings", title: "Keybindings", diff --git a/native/t3-chrome-extension/background.js b/native/t3-chrome-extension/background.js index 61ade6b7e2a4..6123e426d32b 100644 --- a/native/t3-chrome-extension/background.js +++ b/native/t3-chrome-extension/background.js @@ -1,4 +1,4 @@ -// T3 Code desktop control — Chrome side. +// MT Code desktop control — Chrome side. // // The agent works only in tabs it created, collected into a labelled tab group, // so the user's own tabs are never touched and they can keep browsing while a @@ -11,7 +11,7 @@ // carries the originating request id. const HOST = "com.t3tools.t3code.desktop"; -const GROUP_TITLE = "T3 Code"; +const GROUP_TITLE = "MT Code"; const OWNED_STATE_KEY = "ownedState"; /** Tabs this extension owns, and the group holding them. */ @@ -634,7 +634,7 @@ async function navigate(tabId, url) { // // Toolbar icon = T3 logo (manifest icons/). Tab favicon = Computer Use badge // (icons/pointer-*.png), matching Settings → Agent cursor overlay — so a tab -// in the "T3 Code" group is visually distinct from the extension itself. +// in the "MT Code" group is visually distinct from the extension itself. // // An extension cannot set a tab's favicon directly, but it can replace the // page's icon link, which is what Chrome renders in the tab strip. Pages diff --git a/native/t3-chrome-extension/icons/icon-128.png b/native/t3-chrome-extension/icons/icon-128.png index 9f1e1895706f850be224524dcec800810f5ccb2b..f9a944eda5aaf8caf89d95977e6c2d3576e82674 100644 GIT binary patch literal 17031 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%0DHyP^-*%u}+lv_l{5wsBfM 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 E0SI}aRsaA1 diff --git a/native/t3-chrome-extension/icons/icon-16.png b/native/t3-chrome-extension/icons/icon-16.png index 841a2e479cbc15d98e4dfb846148e1284388f128..adf444cd1b8fb0243f1691f89751dd2db3687eb2 100644 GIT binary patch delta 871 zcmV-t1DO1w1^ot)BYyw^b5ch_0Itp)=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031 z000^Q000001E2u_0{{R30RRC20H6W@1ONa40RR915TFA91ONa40RR915C8xG05nx` z@&Et==Sf6CR5%f>Qd>wAQ5gQt%30Y7EQArRH z`O*~%xLmmTd#O-Ld-IZ6k!h);O)j+=O6o1%G;8OrZ77Q#|QtxBCs==>o@c)XBL_ z8zCbhIW`>AWg#&>Mm)_e{m9E-3_`rPdZ`#nno<=b~)92U*0t3l=H9!Fh84DOUwApiftQ!Va|FQF z)>gUMZ2o+Bc=+Dy^-9)S-&%{c7Gum{8e>vx?TZlNcMt?W;(s`9(`Yoxl}e>8rF6iT zt+j4PQB+oO9RIqwxcDZe%#>16DJ7IrD5a26B7{I|jg<1kP6&}3V@9pD)6C4wJOlRK zUu|FktCV^cBO@c(;^LxSUtcH7GLj@gYfTUYJU%{hdV0#*+8Wkc@;oO=60WbW+1}np zYn|&fP4&dY1b@rR%j9{^!omXmexIwWEAH;@xVgEZTCLLUb{QQVrBbQTY&JgvG~?sr zgKC9B!F9V`x3jbJ&+3PVhwk9uz!eGwCxmcALqkp)V-P|hgdhwJWlwH7HQd4HY*aCUY^r_-TcuQNM4ORZKz zN{Kec@VB0xo_KqE8w3g=aL%#0xd}j{(O`9Tm140-r_=cyAf+UZV|u+F0LB<FH^z)ha>=PEJnP z+uI|{GJnD_%<(+$OCWik=X~G)1k%{p*s}zV00HFh%TM~iIm2LBH~;_u diff --git a/native/t3-chrome-extension/icons/icon-32.png b/native/t3-chrome-extension/icons/icon-32.png index 96b9d50107a8a37fd7a322f86e125a114e8c2e10..31c7a4f39c64e42877a383730b3dc6d017a1749b 100644 GIT binary patch delta 2193 zcmV;C2yXYV44e^=BYyw^b5ch_0Itp)=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031 z000^Q000001E2u_0{{R30RRC20H6W@1ONa40RR91AfN*P1ONa40RR91AOHXW0IY^$ z^8f${21!IgR9Fe^R#|LZR~i29GTWW;%-G|x9XrHJoTPDMCx0Pzpu%*esRJcN(5g}j z5<-fApg^HCeL;QUrQ#)3#UfOZf(iktDvBCe%~G`lwe`pOx{l4P<>!9_@VsvScMlzVrmOqL z!LhNa>YQoAbv$^Un6B`nuJo$v!}6slIuu3Y#d#jTuETrprl#T7QKRQbXJ=>r<3Rok zKyPnv;AcPo)hiw6E`4iaVv5E!sM4s)UX{+pe~P@?Uxf%p#7q)cXc0%i#|^-`z-*Fnn~B?a)0?n+UZz02ZdKxvmRf1=w7`3#u?nY zHGv=h=m{Kt_S5@;hXy(S+-fw4-SmLr5@t(Dulu@sNO=|;H^_Da|!^M zmK}GQUV?VH@~nje-+2O<6!Z_=MsLqBu3R0*&fSlrq`V5y0?1}2;up^!SpiU>0mpF+fmS#dAD6Li+_p(N z7jJxR--D>xP$~agwotvH9IyPY4G*_IkAvSkiuA&w$WNc@#=##P#q~ZP?rCl!i*eLe zM^RZ>WGufc`T_u+<56LY?!};{swgcj7Js+0eg1RX@$~*LBNi)SiV2{=x^=}!rRES* z)}z$dfb{%gs&C!01$*{9g{rDbiZ)LHI#Rqwz*pe2AOxbi$+(LUF!ny5jmzauELDrn5v6@aa>N~&ws}N zu$HbcpwK`NiITXStC9_|>S#0~d*NKPin0={k5@}79co}^G6QT*FcUPGnH(IBxT+${ zwd!L*(iRz#FY~7WfI|jUELJ|ja3ma-bc@OJ^9wX;Fg-vhn?G^ z(0v+dw=q68gWtb;g3f#K=VPbQ)Ko9E5TNlFUO0f}-FKs=mbosQhk^bPu{AT3SdO`O z2mqoq`~1OW-?A?NU|SY~{(lgnEIQ(a>J4$m8^~l#WYQ+yZ|}m}r#_IC@{27yBrqYy zC!g9QjoD5fmoN3;FMm9Zw@!3mc4h&7E`Z^uxEWgZI>o%Y6#z&i3|IHX;N(r%L|>K+ z$rnw_LCwZyTZ%dY=p_6Y)j5XY70@T+_ss{*%j}+DFBd3XHxz^m<-r51cNtc zDMF8G&cQNWG;G^}rhgq(7#_ZZfBbzI9q*1}WN-lrL5KVXn(AB7Hxk6?M2`&9q9ZPa z+>3>Y%BuU6)S3WNGqZz{D9bE!omAo6#c7rIR4U z5kwYf(s^gp!Y!^?qrE_YKjNIo@j#G=53bUhH>K*=71yFK`w`z2f+GNqv3$XZ1;(84 z?1!rX6dcKh``@46bl0O%O^@DhnXKWgj6x5ty{HTEEZ7ktvbbuCX9qiH!(rU9q#IAf zn$9Lt!^Fq`*?(*vPK)9ZRMo_fyng(*Z>|YI3^?Y*2gN0KZ_(B0R@pe#;f;i*3B$T_g#-q27#ec=8`w`%Q)F+AyORbOjC{j)I zem=*-aeOAZ0cGX!+o4kXmzLJ|Tw7b)yzsHQ33aU58XLb+S5a}#u7Db;Wi^XY#RdzB zsx!1cDq5tup8^%NMdKwRt|$)o9XF#YZpu^5fvj_<^Eby{>|fK#rvslf1OEmL$#xXw TBFH8H015yANkvXXu0mjfxZNTX delta 1568 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
GX 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 diff --git a/native/t3-chrome-extension/install.ps1 b/native/t3-chrome-extension/install.ps1 index afe6bb9cc703..073421c6ccb3 100644 --- a/native/t3-chrome-extension/install.ps1 +++ b/native/t3-chrome-extension/install.ps1 @@ -40,7 +40,7 @@ $utf8NoBom = New-Object System.Text.UTF8Encoding $false $manifestPath = Join-Path $support "$HostName.json" $manifest = [ordered]@{ name = $HostName - description = 'T3 Code desktop control bridge' + description = 'MT Code desktop control bridge' path = $wrapper type = 'stdio' allowed_origins = @("chrome-extension://$ExtensionId/") diff --git a/native/t3-chrome-extension/install.sh b/native/t3-chrome-extension/install.sh index 2806e53f0245..1caaa28471ec 100755 --- a/native/t3-chrome-extension/install.sh +++ b/native/t3-chrome-extension/install.sh @@ -63,7 +63,7 @@ for profile in "$@"; do cat > "$dir/$HOST_NAME.json" <"], + "host_permissions": [ + "" + ], "background": { "service_worker": "background.js" }, "content_scripts": [ { - "matches": [""], - "js": ["wake.js"], + "matches": [ + "" + ], + "js": [ + "wake.js" + ], "run_at": "document_start", "all_frames": false } @@ -41,7 +47,9 @@ "icons/pointer-48.png", "icons/pointer-64.png" ], - "matches": [""] + "matches": [ + "" + ] } ] } diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 613cff012300..7c4c465a9278 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -114,6 +114,39 @@ export const TerminalFontSize = Schema.Int.check( export type TerminalFontSize = typeof TerminalFontSize.Type; export const DEFAULT_TERMINAL_FONT_SIZE: TerminalFontSize = 12; +/** + * Sidebar header artwork. + * + * Upstream ties the artwork to the Dev/Nightly channel; MT Code ships one + * release, so the artwork is a choice instead of a side effect of a build + * channel. "auto" keeps the upstream behaviour (art on Dev/Nightly, nothing + * on a release build); the named scenes and any custom artwork are explicit. + */ +export const SIDEBAR_ARTWORK_BUILT_IN_IDS = ["auto", "none", "night", "day"] as const; +export const SidebarArtworkSelection = TrimmedNonEmptyString.check( + Schema.isMaxLength(80), +); +export type SidebarArtworkSelection = typeof SidebarArtworkSelection.Type; +export const DEFAULT_SIDEBAR_ARTWORK_SELECTION = "auto"; + +/** Largest artwork a user may store, so settings stay a settings file. */ +export const MAX_CUSTOM_SIDEBAR_ARTWORK_BYTES = 512 * 1024; + +/** + * A user's own artwork. Stored on the server so it follows the account to + * every client attached to it - desktop, browser, phone - rather than living + * in one device's local storage. + */ +export const CustomSidebarArtwork = Schema.Struct({ + id: TrimmedNonEmptyString.check(Schema.isMaxLength(80)), + name: TrimmedNonEmptyString.check(Schema.isMaxLength(60)), + /** `data:` URL for an image (PNG, JPEG, WEBP, or SVG). */ + image: TrimmedNonEmptyString.check(Schema.isMaxLength(MAX_CUSTOM_SIDEBAR_ARTWORK_BYTES)), + /** ISO timestamp; a plain string so the settings schema stays service-free. */ + createdAt: TrimmedNonEmptyString.check(Schema.isMaxLength(40)), +}); +export type CustomSidebarArtwork = typeof CustomSidebarArtwork.Type; + export const EnvironmentIdentificationMode = Schema.Literals(["artwork", "pill", "none"]); export type EnvironmentIdentificationMode = typeof EnvironmentIdentificationMode.Type; export const DEFAULT_ENVIRONMENT_IDENTIFICATION_MODE: EnvironmentIdentificationMode = "artwork"; @@ -763,6 +796,14 @@ export const ServerSettings = Schema.Struct({ environmentLabel: TrimmedString.check(Schema.isMaxLength(40)).pipe( Schema.withDecodingDefault(Effect.succeed("")), ), + /** Which sidebar artwork this account shows; see SidebarArtworkSelection. */ + sidebarArtwork: SidebarArtworkSelection.pipe( + Schema.withDecodingDefault(Effect.succeed(DEFAULT_SIDEBAR_ARTWORK_SELECTION)), + ), + /** The account's own artwork, synced to every client on this server. */ + customSidebarArtworks: Schema.Array(CustomSidebarArtwork).pipe( + Schema.withDecodingDefault(Effect.succeed([])), + ), // Legacy token-by-token assistant output. Deliberately a fresh key (was // `enableAssistantStreaming`): decoding drops the old key, so everyone, // including prior opt-ins, resets to the buffered default. @@ -1015,6 +1056,8 @@ const AntigravitySettingsPatch = Schema.Struct({ export const ServerSettingsPatch = Schema.Struct({ // Server settings environmentLabel: Schema.optionalKey(TrimmedString.check(Schema.isMaxLength(40))), + sidebarArtwork: Schema.optionalKey(SidebarArtworkSelection), + customSidebarArtworks: Schema.optionalKey(Schema.Array(CustomSidebarArtwork)), enableLegacyTokenStreaming: Schema.optionalKey(Schema.Boolean), enableProviderUpdateChecks: Schema.optionalKey(Schema.Boolean), enableAgentBrowserAccess: Schema.optionalKey(Schema.Boolean), diff --git a/scripts/personal-publish-web.sh b/scripts/personal-publish-web.sh index 76a68f5ac275..54339e4ba146 100755 --- a/scripts/personal-publish-web.sh +++ b/scripts/personal-publish-web.sh @@ -32,10 +32,18 @@ if [[ "$MUNIM_CONNECT_ACTIVE" != 1 ]]; then echo "building without Munim Clerk; T3 Connect will still be offered as an external option" fi -# Match the desktop munim distro: Nightly stage (sidebar artwork) with a plain -# "MT Code" shell name, no "(Alpha)"/"(Nightly)" suffix. -VITE_HOSTED_APP_URL="$HOSTED_URL" VITE_APP_BASE_NAME="MT Code" \ - VITE_APP_STAGE_LABEL="Nightly" VITE_APP_DISPLAY_NAME="MT Code" \ +# Same single version the desktop builds stamp, so About on the hosted app +# matches the installers instead of reporting the checked-in package version. +# shellcheck source=lib/personal-mt-version.sh +source "$REPO/scripts/lib/personal-mt-version.sh" +personal_mt_export_desktop_version +echo "web version $T3CODE_DESKTOP_VERSION" + +# Match the desktop munim distro: plain "MT Code", one release, no stage +# suffix. Sidebar artwork is a setting now, not a side effect of a channel. +APP_VERSION="$T3CODE_DESKTOP_VERSION" \ + VITE_HOSTED_APP_URL="$HOSTED_URL" VITE_APP_BASE_NAME="MT Code" \ + VITE_APP_DISPLAY_NAME="MT Code" \ vp run --filter @t3tools/web build node scripts/apply-web-brand-assets.ts munim apps/web/dist