@the-portland-company/shell 0.94.0 → 0.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/app-chrome.cjs +105 -46
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +2 -1
- package/dist/app-chrome.d.ts +2 -1
- package/dist/app-chrome.js +5 -49
- package/dist/app-chrome.js.map +1 -1
- package/dist/app-icons.cjs +9 -0
- package/dist/app-icons.cjs.map +1 -1
- package/dist/app-icons.d.cts +5 -3
- package/dist/app-icons.d.ts +5 -3
- package/dist/app-icons.js +9 -1
- package/dist/app-icons.js.map +1 -1
- package/dist/brand-favicon.cjs +87 -0
- package/dist/brand-favicon.cjs.map +1 -0
- package/dist/brand-favicon.d.cts +18 -0
- package/dist/brand-favicon.d.ts +18 -0
- package/dist/brand-favicon.js +4 -0
- package/dist/brand-favicon.js.map +1 -0
- package/dist/{chunk-MCMD4WR6.js → chunk-BA3GITRQ.js} +3 -3
- package/dist/{chunk-MCMD4WR6.js.map → chunk-BA3GITRQ.js.map} +1 -1
- package/dist/chunk-BMR5WAWH.js +111 -0
- package/dist/chunk-BMR5WAWH.js.map +1 -0
- package/dist/{index-ByUbBbta.d.cts → index-AIzWNzl-.d.cts} +1 -0
- package/dist/{index-PRu56mmW.d.ts → index-Byjq0Whp.d.ts} +1 -0
- package/dist/index.cjs +79 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/native.cjs +77 -0
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +2 -1
- package/dist/native.d.ts +2 -1
- package/dist/native.js +3 -2
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## 0.95.0
|
|
2
|
+
|
|
3
|
+
Every app's browser tab shows the Brand's VRM tile (Brand `assets.favicon`; Reference/UI-Platform conformance: "Favicons resolve from Brand `assets.favicon`, not static files").
|
|
4
|
+
|
|
5
|
+
- **New `BrandFavicon`** (root and `/native`), rendered by `PolitogyAppFrame`, so every app on the frame inherits it with no code change. After mount it reads `--asset-favicon` and `--asset-apple-icon` from the runtime token sheet (`/api/tokens?format=css`, already loaded by the browser in every app) and sets `<link rel="icon">` and `<link rel="apple-touch-icon">`. The link is kept the last icon link in `<head>`, so a stale metadata icon or a leftover static file never wins. Draws nothing until the sheet defines the asset.
|
|
6
|
+
- Why: `politogyAppIcons()` resolves icons on the server, and in production Cloudflare's bot check answers the Worker's request to `specs.politogyvrm.com/api/icons/<app>` with a challenge, so the metadata had no icon and tabs showed the browser default or an app's leftover Next.js `favicon.ico` triangle.
|
|
7
|
+
- **`politogyAppIcons()` fallback:** when the API can't be reached or has no favicon, it now returns `SPECS_FAVICON_URL` (`https://specs.politogyvrm.com/api/favicon`, Specs' public 302 to Brand `assets.favicon`; `?kind=apple` for the iOS icon) instead of no entries. The icon link is in the server HTML, so the browser never asks for `/favicon.ico` and never flashes a blank tab. Exported: `SPECS_FAVICON_URL`.
|
|
8
|
+
- Also exported: `applyBrandFavicon()`, `watchBrandFavicon()`, `BRAND_FAVICON_LINK_ID`, `BRAND_APPLE_ICON_LINK_ID`, for apps outside the frame.
|
|
9
|
+
- **New subpath `@the-portland-company/shell/brand-favicon`** (client entry, React only): import `BrandFavicon` from it in a root layout to cover pages that do not render the frame (public forms, surveys, polls), without pulling in `./native`.
|
|
10
|
+
- Apps should delete any `app/favicon.ico` or `app/icon.svg` they still carry.
|
|
11
|
+
- Tests: `__tests__/brandFavicon.test.tsx`.
|
|
12
|
+
|
|
1
13
|
## 0.94.0
|
|
2
14
|
|
|
3
15
|
One shared look for the three "no AI Credits" states (VRM-Admin/Pricing-and-Metering §8.6 and §8.10.3; Ben's go on the AI metering plan, 2026-10-24).
|
package/dist/app-chrome.cjs
CHANGED
|
@@ -2176,6 +2176,110 @@ var NAV_DISPLAY_OPTIONS = [
|
|
|
2176
2176
|
{ value: "tabs", label: "Tabs Only" },
|
|
2177
2177
|
{ value: "both", label: "Both" }
|
|
2178
2178
|
];
|
|
2179
|
+
function parseCssUrl(value) {
|
|
2180
|
+
if (!value) return null;
|
|
2181
|
+
const m = /^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(value.trim());
|
|
2182
|
+
const url = m?.[2]?.trim();
|
|
2183
|
+
return url && /^https:\/\//i.test(url) ? url : null;
|
|
2184
|
+
}
|
|
2185
|
+
function readBrandAsset(kind) {
|
|
2186
|
+
if (typeof document === "undefined") return null;
|
|
2187
|
+
try {
|
|
2188
|
+
return parseCssUrl(
|
|
2189
|
+
window.getComputedStyle(document.documentElement).getPropertyValue(`--asset-${kind}`)
|
|
2190
|
+
);
|
|
2191
|
+
} catch {
|
|
2192
|
+
return null;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
var EMPTY = { logoHorizontal: null, logoSquare: null };
|
|
2196
|
+
var RETRY_MS = [0, 250, 1e3, 3e3];
|
|
2197
|
+
function useBrandAssets() {
|
|
2198
|
+
const [assets, setAssets] = react.useState(EMPTY);
|
|
2199
|
+
react.useEffect(() => {
|
|
2200
|
+
let done = false;
|
|
2201
|
+
const read = () => {
|
|
2202
|
+
if (done) return;
|
|
2203
|
+
const next = {
|
|
2204
|
+
logoHorizontal: readBrandAsset("logo-horizontal"),
|
|
2205
|
+
logoSquare: readBrandAsset("logo-square")
|
|
2206
|
+
};
|
|
2207
|
+
setAssets(
|
|
2208
|
+
(prev) => prev.logoHorizontal === next.logoHorizontal && prev.logoSquare === next.logoSquare ? prev : next
|
|
2209
|
+
);
|
|
2210
|
+
};
|
|
2211
|
+
const links = Array.from(
|
|
2212
|
+
document.querySelectorAll('link[rel="stylesheet"][href*="api/tokens"]')
|
|
2213
|
+
);
|
|
2214
|
+
links.forEach((l) => l.addEventListener("load", read));
|
|
2215
|
+
const timers = RETRY_MS.map((ms) => window.setTimeout(read, ms));
|
|
2216
|
+
read();
|
|
2217
|
+
return () => {
|
|
2218
|
+
done = true;
|
|
2219
|
+
links.forEach((l) => l.removeEventListener("load", read));
|
|
2220
|
+
timers.forEach((t) => window.clearTimeout(t));
|
|
2221
|
+
};
|
|
2222
|
+
}, []);
|
|
2223
|
+
return assets;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
// src/lib/brandFavicon.ts
|
|
2227
|
+
var BRAND_FAVICON_LINK_ID = "politogy-brand-favicon";
|
|
2228
|
+
var BRAND_APPLE_ICON_LINK_ID = "politogy-brand-apple-icon";
|
|
2229
|
+
function upsertLink(id, rel, href) {
|
|
2230
|
+
let link = document.getElementById(id);
|
|
2231
|
+
if (!link) {
|
|
2232
|
+
link = document.createElement("link");
|
|
2233
|
+
link.id = id;
|
|
2234
|
+
link.rel = rel;
|
|
2235
|
+
}
|
|
2236
|
+
if (link.getAttribute("href") !== href) link.href = href;
|
|
2237
|
+
const icons = document.head.querySelectorAll(`link[rel~="${rel.split(" ")[0]}"]`);
|
|
2238
|
+
if (icons[icons.length - 1] !== link) document.head.appendChild(link);
|
|
2239
|
+
}
|
|
2240
|
+
function applyBrandFavicon() {
|
|
2241
|
+
if (typeof document === "undefined") return false;
|
|
2242
|
+
const favicon = readBrandAsset("favicon");
|
|
2243
|
+
if (!favicon) return false;
|
|
2244
|
+
upsertLink(BRAND_FAVICON_LINK_ID, "icon", favicon);
|
|
2245
|
+
const apple = readBrandAsset("apple-icon");
|
|
2246
|
+
if (apple) upsertLink(BRAND_APPLE_ICON_LINK_ID, "apple-touch-icon", apple);
|
|
2247
|
+
return true;
|
|
2248
|
+
}
|
|
2249
|
+
var RETRY_MS2 = [0, 250, 1e3, 3e3];
|
|
2250
|
+
function watchBrandFavicon() {
|
|
2251
|
+
if (typeof document === "undefined") return () => {
|
|
2252
|
+
};
|
|
2253
|
+
let done = false;
|
|
2254
|
+
const apply = () => {
|
|
2255
|
+
if (!done) applyBrandFavicon();
|
|
2256
|
+
};
|
|
2257
|
+
const sheets = /* @__PURE__ */ new Set();
|
|
2258
|
+
const listen = () => {
|
|
2259
|
+
document.querySelectorAll('link[rel="stylesheet"][href*="api/tokens"]').forEach((l) => {
|
|
2260
|
+
if (sheets.has(l)) return;
|
|
2261
|
+
sheets.add(l);
|
|
2262
|
+
l.addEventListener("load", apply);
|
|
2263
|
+
});
|
|
2264
|
+
};
|
|
2265
|
+
listen();
|
|
2266
|
+
const timers = RETRY_MS2.map((ms) => window.setTimeout(apply, ms));
|
|
2267
|
+
const observer = typeof MutationObserver === "undefined" ? null : new MutationObserver(() => {
|
|
2268
|
+
listen();
|
|
2269
|
+
apply();
|
|
2270
|
+
});
|
|
2271
|
+
observer?.observe(document.head, { childList: true });
|
|
2272
|
+
return () => {
|
|
2273
|
+
done = true;
|
|
2274
|
+
sheets.forEach((l) => l.removeEventListener("load", apply));
|
|
2275
|
+
timers.forEach((t) => window.clearTimeout(t));
|
|
2276
|
+
observer?.disconnect();
|
|
2277
|
+
};
|
|
2278
|
+
}
|
|
2279
|
+
function BrandFavicon() {
|
|
2280
|
+
react.useEffect(() => watchBrandFavicon(), []);
|
|
2281
|
+
return null;
|
|
2282
|
+
}
|
|
2179
2283
|
var standardNavIcons = {
|
|
2180
2284
|
home: lu.LuHouse,
|
|
2181
2285
|
users: lu.LuUsers,
|
|
@@ -4610,52 +4714,6 @@ function writeUrlState(current, patch) {
|
|
|
4610
4714
|
}
|
|
4611
4715
|
return buildSearch(state);
|
|
4612
4716
|
}
|
|
4613
|
-
function parseCssUrl(value) {
|
|
4614
|
-
if (!value) return null;
|
|
4615
|
-
const m = /^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(value.trim());
|
|
4616
|
-
const url = m?.[2]?.trim();
|
|
4617
|
-
return url && /^https:\/\//i.test(url) ? url : null;
|
|
4618
|
-
}
|
|
4619
|
-
function readBrandAsset(kind) {
|
|
4620
|
-
if (typeof document === "undefined") return null;
|
|
4621
|
-
try {
|
|
4622
|
-
return parseCssUrl(
|
|
4623
|
-
window.getComputedStyle(document.documentElement).getPropertyValue(`--asset-${kind}`)
|
|
4624
|
-
);
|
|
4625
|
-
} catch {
|
|
4626
|
-
return null;
|
|
4627
|
-
}
|
|
4628
|
-
}
|
|
4629
|
-
var EMPTY = { logoHorizontal: null, logoSquare: null };
|
|
4630
|
-
var RETRY_MS = [0, 250, 1e3, 3e3];
|
|
4631
|
-
function useBrandAssets() {
|
|
4632
|
-
const [assets, setAssets] = react.useState(EMPTY);
|
|
4633
|
-
react.useEffect(() => {
|
|
4634
|
-
let done = false;
|
|
4635
|
-
const read = () => {
|
|
4636
|
-
if (done) return;
|
|
4637
|
-
const next = {
|
|
4638
|
-
logoHorizontal: readBrandAsset("logo-horizontal"),
|
|
4639
|
-
logoSquare: readBrandAsset("logo-square")
|
|
4640
|
-
};
|
|
4641
|
-
setAssets(
|
|
4642
|
-
(prev) => prev.logoHorizontal === next.logoHorizontal && prev.logoSquare === next.logoSquare ? prev : next
|
|
4643
|
-
);
|
|
4644
|
-
};
|
|
4645
|
-
const links = Array.from(
|
|
4646
|
-
document.querySelectorAll('link[rel="stylesheet"][href*="api/tokens"]')
|
|
4647
|
-
);
|
|
4648
|
-
links.forEach((l) => l.addEventListener("load", read));
|
|
4649
|
-
const timers = RETRY_MS.map((ms) => window.setTimeout(read, ms));
|
|
4650
|
-
read();
|
|
4651
|
-
return () => {
|
|
4652
|
-
done = true;
|
|
4653
|
-
links.forEach((l) => l.removeEventListener("load", read));
|
|
4654
|
-
timers.forEach((t) => window.clearTimeout(t));
|
|
4655
|
-
};
|
|
4656
|
-
}, []);
|
|
4657
|
-
return assets;
|
|
4658
|
-
}
|
|
4659
4717
|
var CORE_APP = "vrm";
|
|
4660
4718
|
var DEFAULT_SIGN_OUT_URL = "/auth/signout";
|
|
4661
4719
|
var POLITOGY_APPS = {
|
|
@@ -4970,6 +5028,7 @@ function FrameBody({
|
|
|
4970
5028
|
);
|
|
4971
5029
|
const breadcrumbs = core ? core.page?.breadcrumbs : grants?.includes("breadcrumbs") ? granted("breadcrumbs") : derivedBreadcrumbs;
|
|
4972
5030
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
5031
|
+
/* @__PURE__ */ jsxRuntime.jsx(BrandFavicon, {}),
|
|
4973
5032
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4974
5033
|
LoadingScreen,
|
|
4975
5034
|
{
|