@jini-ai/admin 0.4.7 → 0.4.8
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
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.8 — 2026-10-13
|
|
4
|
+
|
|
5
|
+
- Share-image warning checks decoded size against 1200×630 (width and height independently; was width ≥ 600). New copy key: `The share image is only {height} px tall; use an image at least 630 px tall.`; the width key now says 1200 px.
|
|
6
|
+
- SEO rules import `interpolate` from the universal `@jini-ai/ui/i18n`, so the SEO entries stay React-free. UI peer floor ^0.5.1.
|
|
7
|
+
|
|
3
8
|
## 0.4.7 — 2026-10-13
|
|
4
9
|
|
|
5
10
|
- Accept UI ^0.5.0 for the chart contract extraction.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AdminSeoPort } from '../../ports.js';
|
|
2
2
|
import type { MediaRefFieldController, SeoTranslator } from '../../models.js';
|
|
3
|
-
/** Probe original file size through the host port; decoded
|
|
3
|
+
/** Probe original file size through the host port; decoded dimensions come from the existing preview.
|
|
4
4
|
* Unknown size is not zero. URL-keyed state and cancellation prevent a prior choice's response
|
|
5
5
|
* or image event from warning against the current selection. The probe has a 10-second bound.
|
|
6
6
|
* External HEAD responses need CORS, so only same-origin files are probed; decoded previews
|
|
7
|
-
* still report external loading failures and known
|
|
7
|
+
* still report external loading failures and known dimensions. HTTP copy is translated at render.
|
|
8
8
|
* @param required Controlled reference, source preview URL and injected SEO API.
|
|
9
9
|
* @returns Non-blocking warning and the preview's native load/error handlers.
|
|
10
10
|
* @complexity O(1) state and one HEAD per selection; no image bytes are downloaded by the probe.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { interpolate } from '@jini-ai/ui/
|
|
2
|
+
import { interpolate } from '@jini-ai/ui/i18n';
|
|
3
3
|
import { shareImageWarning } from '../../rules.js';
|
|
4
|
-
/** Probe original file size through the host port; decoded
|
|
4
|
+
/** Probe original file size through the host port; decoded dimensions come from the existing preview.
|
|
5
5
|
* Unknown size is not zero. URL-keyed state and cancellation prevent a prior choice's response
|
|
6
6
|
* or image event from warning against the current selection. The probe has a 10-second bound.
|
|
7
7
|
* External HEAD responses need CORS, so only same-origin files are probed; decoded previews
|
|
8
|
-
* still report external loading failures and known
|
|
8
|
+
* still report external loading failures and known dimensions. HTTP copy is translated at render.
|
|
9
9
|
* @param required Controlled reference, source preview URL and injected SEO API.
|
|
10
10
|
* @returns Non-blocking warning and the preview's native load/error handlers.
|
|
11
11
|
* @complexity O(1) state and one HEAD per selection; no image bytes are downloaded by the probe.
|
|
@@ -62,12 +62,12 @@ export function useShareImageWarning({ value, previewUrl, api }, { t = key => ke
|
|
|
62
62
|
const checked = inspection?.url === previewUrl ? inspection : null;
|
|
63
63
|
const error = checked?.error;
|
|
64
64
|
return {
|
|
65
|
-
warning: shareImageWarning({ value, previewUrl, width: source?.width, previewFailed: source?.failed, byteSize: checked?.byteSize,
|
|
65
|
+
warning: shareImageWarning({ value, previewUrl, width: source?.width, height: source?.height, previewFailed: source?.failed, byteSize: checked?.byteSize,
|
|
66
66
|
error: error ? interpolate({ template: t(error.key, error.vars), vars: error.vars ?? {} }) : undefined }, { t }),
|
|
67
67
|
onPreviewLoad: ({ currentTarget }) => {
|
|
68
68
|
const url = currentTarget.getAttribute('src');
|
|
69
69
|
if (url && url === currentUrl.current)
|
|
70
|
-
setDecoded({ url, width: currentTarget.naturalWidth });
|
|
70
|
+
setDecoded({ url, width: currentTarget.naturalWidth, height: currentTarget.naturalHeight });
|
|
71
71
|
},
|
|
72
72
|
onPreviewError: ({ currentTarget }) => {
|
|
73
73
|
const url = currentTarget.getAttribute('src');
|
package/dist/seo/rules.d.ts
CHANGED
|
@@ -121,14 +121,15 @@ export declare function resolveMediaRefPreviewUrl({ value, mediaOriginalUrl }: {
|
|
|
121
121
|
siteOrigin?: string | undefined;
|
|
122
122
|
}): string | null;
|
|
123
123
|
/** Diagnostic policy: unknown measurements stay unknown; measured failures name their reason.
|
|
124
|
-
* @param required Controlled field, decoded preview
|
|
124
|
+
* @param required Controlled field, decoded preview dimensions and original HTTP metadata.
|
|
125
125
|
* @returns A non-blocking explanation, or null for an image without a measured problem.
|
|
126
126
|
* @complexity O(n) in value/error length; fixed-count checks, no I/O.
|
|
127
127
|
*/
|
|
128
|
-
export declare function shareImageWarning({ value, previewUrl, width, byteSize, error, previewFailed }: {
|
|
128
|
+
export declare function shareImageWarning({ value, previewUrl, width, height, byteSize, error, previewFailed }: {
|
|
129
129
|
value: string;
|
|
130
130
|
previewUrl: string | null;
|
|
131
131
|
width?: number | undefined;
|
|
132
|
+
height?: number | undefined;
|
|
132
133
|
byteSize?: number | null | undefined;
|
|
133
134
|
error?: string | null | undefined;
|
|
134
135
|
previewFailed?: boolean | undefined;
|
package/dist/seo/rules.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { interpolate } from '@jini-ai/ui/
|
|
1
|
+
import { interpolate } from '@jini-ai/ui/i18n';
|
|
2
2
|
/**
|
|
3
3
|
* @file Pure logic for the `seo` feature — everything that computes a value rather than rendering
|
|
4
4
|
* one.
|
|
@@ -180,11 +180,11 @@ function localRenditionAssetKey({ value, siteOrigin }, _optional = {}) {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
/** Diagnostic policy: unknown measurements stay unknown; measured failures name their reason.
|
|
183
|
-
* @param required Controlled field, decoded preview
|
|
183
|
+
* @param required Controlled field, decoded preview dimensions and original HTTP metadata.
|
|
184
184
|
* @returns A non-blocking explanation, or null for an image without a measured problem.
|
|
185
185
|
* @complexity O(n) in value/error length; fixed-count checks, no I/O.
|
|
186
186
|
*/
|
|
187
|
-
export function shareImageWarning({ value, previewUrl, width, byteSize, error, previewFailed }, { t = key => key } = {}) {
|
|
187
|
+
export function shareImageWarning({ value, previewUrl, width, height, byteSize, error, previewFailed }, { t = key => key } = {}) {
|
|
188
188
|
if (typeof value !== 'string' || !value.trim())
|
|
189
189
|
return t('No share image is selected. Link previews may show no picture.');
|
|
190
190
|
if (!previewUrl)
|
|
@@ -194,8 +194,10 @@ export function shareImageWarning({ value, previewUrl, width, byteSize, error, p
|
|
|
194
194
|
if (previewFailed)
|
|
195
195
|
return t('The share image could not be loaded. Choose an available image.');
|
|
196
196
|
const reasons = [];
|
|
197
|
-
if (width !== undefined && width <
|
|
198
|
-
reasons.push(interpolate({ template: t('The share image is only {width} px wide; use an image at least
|
|
197
|
+
if (width !== undefined && width < 1200)
|
|
198
|
+
reasons.push(interpolate({ template: t('The share image is only {width} px wide; use an image at least 1200 px wide.', { width }), vars: { width } }));
|
|
199
|
+
if (height !== undefined && height < 630)
|
|
200
|
+
reasons.push(interpolate({ template: t('The share image is only {height} px tall; use an image at least 630 px tall.', { height }), vars: { height } }));
|
|
199
201
|
if (byteSize !== undefined && byteSize !== null && byteSize < 5120)
|
|
200
202
|
reasons.push(interpolate({ template: t('The share image is only {byteSize} bytes; use an image of at least 5 KB (5120 bytes).', { byteSize }), vars: { byteSize } }));
|
|
201
203
|
return reasons.length ? reasons.join(' ') : null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/admin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Composable admin surface for Jini-hosted products: framework-free contracts, panel registry, and route model in `/core`; React shell and panels in `/react`. Import only the layers you need.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -434,7 +434,7 @@
|
|
|
434
434
|
"react": "^18.3.0 || ^19.0.0",
|
|
435
435
|
"react-dom": "^18.3.0 || ^19.0.0",
|
|
436
436
|
"@jini-ai/agentic": "^0.4.4",
|
|
437
|
-
"@jini-ai/ui": "^0.
|
|
437
|
+
"@jini-ai/ui": "^0.5.1",
|
|
438
438
|
"@jini-ai/ui-kit": "^0.1.3",
|
|
439
439
|
"@jini-ai/agent-plugins": "^0.4.3",
|
|
440
440
|
"@jini-ai/cms": "^0.5.6",
|
|
@@ -476,12 +476,12 @@
|
|
|
476
476
|
"react": "^19.2.0",
|
|
477
477
|
"react-dom": "^19.2.0",
|
|
478
478
|
"@testing-library/user-event": "^14.6.1",
|
|
479
|
-
"@jini-ai/ui-kit": "^0.1.3",
|
|
480
|
-
"@jini-ai/core": "^0.4.4",
|
|
481
479
|
"@jini-ai/agentic": "^0.4.4",
|
|
480
|
+
"@jini-ai/agent-plugins": "^0.4.6",
|
|
481
|
+
"@jini-ai/ui-kit": "^0.1.4",
|
|
482
482
|
"@jini-ai/cms": "^0.5.10",
|
|
483
|
-
"@jini-ai/ui": "^0.5.
|
|
484
|
-
"@jini-ai/
|
|
483
|
+
"@jini-ai/ui": "^0.5.1",
|
|
484
|
+
"@jini-ai/core": "^0.4.5"
|
|
485
485
|
},
|
|
486
486
|
"typesVersions": {
|
|
487
487
|
"*": {
|