@thomas.f/prnext 0.1.16-alpha → 0.1.17-alpha
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/AI.md +63 -0
- package/README.md +384 -9
- package/build/ADAPTERS.md +27 -2
- package/build/actions.mjs +5 -0
- package/build/analyze.mjs +184 -0
- package/build/app-config.mjs +7 -8
- package/build/app-static.mjs +12 -7
- package/build/app.mjs +23 -10
- package/build/build-lock.mjs +6 -6
- package/build/bundle-runtime.mjs +20 -5
- package/build/check.mjs +56 -11
- package/build/compiler.mjs +25 -1
- package/build/concurrent.mjs +12 -0
- package/build/config.mjs +21 -13
- package/build/css-modules.mjs +15 -4
- package/build/dev-compiler-health.mjs +11 -0
- package/build/dev-css-update.mjs +83 -0
- package/build/dev-dependencies.mjs +44 -0
- package/build/dev-discovery.mjs +28 -0
- package/build/dev-worker.mjs +14 -5
- package/build/dev.mjs +55 -4
- package/build/edge.mjs +3 -2
- package/build/framework-imports.mjs +40 -0
- package/build/incremental.mjs +8 -4
- package/build/index.mjs +96 -101
- package/build/inspect.mjs +3 -0
- package/build/inspection-summary.mjs +37 -4
- package/build/loader-cache.mjs +53 -26
- package/build/metadata-routes.mjs +2 -3
- package/build/migrate.mjs +60 -57
- package/build/npm-packages.mjs +3 -18
- package/build/package-manager.mjs +308 -0
- package/build/persistent-cache.mjs +97 -0
- package/build/precompress.mjs +62 -30
- package/build/prerender-pages.mjs +90 -0
- package/build/prerender-worker.mjs +10 -0
- package/build/production-builder.mjs +80 -0
- package/build/scan.mjs +4 -4
- package/build/source-analysis.mjs +42 -0
- package/build/static-adapter.mjs +13 -5
- package/build/static-input.mjs +48 -0
- package/build/styles.mjs +15 -3
- package/build/transform-cache.mjs +37 -4
- package/build/type-command.mjs +41 -0
- package/build/typed-routes.mjs +91 -0
- package/build/typescript-dev-worker.mjs +18 -0
- package/build/typescript-dev.mjs +22 -0
- package/build/typescript-watch.mjs +22 -0
- package/build/typescript.mjs +5 -4
- package/build/webpack-graph.mjs +164 -27
- package/build/worker-preload.mjs +9 -0
- package/cli.mjs +105 -121
- package/compat/index.d.ts +21 -7
- package/compat/link.d.cts +4 -3
- package/compat/navigation.d.cts +4 -3
- package/compat/router.d.cts +4 -3
- package/compat/typed-route.d.cts +17 -0
- package/docs/ai/compatibility.md +106 -0
- package/docs/ai/data.md +128 -0
- package/docs/ai/deployment.md +111 -0
- package/docs/ai/manifest.json +11 -0
- package/docs/ai/quickstart.md +146 -0
- package/docs/ai/routing.md +132 -0
- package/docs/ai/troubleshooting.md +120 -0
- package/index.mjs +2 -0
- package/native/diagnostics.mjs +34 -0
- package/native/resolve.mjs +7 -2
- package/package.json +25 -7
- package/release/ai-docs.mjs +48 -0
- package/release/verify.mjs +2 -0
- package/runtime/ai-docs.mjs +202 -0
- package/runtime/app-client.mjs +17 -6
- package/runtime/app-render.mjs +12 -0
- package/runtime/cloud/archive.mjs +5 -6
- package/runtime/compile-cache.mjs +8 -0
- package/runtime/dev-ai-brands.mjs +26 -0
- package/runtime/dev-ai-style.mjs +99 -0
- package/runtime/dev-build-reasons.mjs +25 -0
- package/runtime/dev-cache-control.mjs +53 -0
- package/runtime/dev-client.mjs +239 -45
- package/runtime/dev-console-copy.mjs +19 -0
- package/runtime/dev-console.mjs +106 -0
- package/runtime/dev-dependency-graph.mjs +40 -0
- package/runtime/dev-element-picker-style.mjs +27 -0
- package/runtime/dev-element-picker.mjs +213 -0
- package/runtime/dev-error-overlay-style.mjs +41 -0
- package/runtime/dev-error-overlay.mjs +167 -0
- package/runtime/dev-error-report.mjs +121 -0
- package/runtime/dev-file-changes.mjs +1 -1
- package/runtime/dev-gateway.mjs +44 -0
- package/runtime/dev-host-relay.mjs +66 -0
- package/runtime/dev-indicator.mjs +186 -0
- package/runtime/dev-issue-motion.mjs +214 -0
- package/runtime/dev-lazy-tool.mjs +18 -0
- package/runtime/dev-logo.mjs +2 -0
- package/runtime/dev-notification.mjs +81 -0
- package/runtime/dev-popout.mjs +34 -0
- package/runtime/dev-position.mjs +107 -0
- package/runtime/dev-preview-touch.mjs +122 -0
- package/runtime/dev-runtime-pool.mjs +45 -0
- package/runtime/dev-runtime-spare.mjs +57 -0
- package/runtime/dev-tools-client.mjs +155 -0
- package/runtime/dev-tools-mcp.mjs +126 -0
- package/runtime/dev-tools-metrics.mjs +31 -0
- package/runtime/dev-tools-model.mjs +42 -0
- package/runtime/dev-tools-panel.mjs +341 -0
- package/runtime/dev-tools-result.mjs +37 -0
- package/runtime/dev-tools-server.mjs +256 -0
- package/runtime/dev-tools-source.mjs +104 -0
- package/runtime/dev-tooltip.mjs +94 -0
- package/runtime/dev-ui-icons.mjs +8 -0
- package/runtime/dev-workbench-style.mjs +106 -0
- package/runtime/dev-workbench.mjs +428 -0
- package/runtime/dev-worker-logs.mjs +31 -0
- package/runtime/development.mjs +307 -0
- package/runtime/env.mjs +2 -1
- package/runtime/pages-client.mjs +10 -1
- package/runtime/pages-navigation.mjs +6 -1
- package/runtime/persistent/client.mjs +6 -5
- package/runtime/persistent/daemon.mjs +66 -56
- package/runtime/persistent/native-proxy.mjs +63 -0
- package/runtime/preload.mjs +12 -0
- package/runtime/rsc-worker.mjs +7 -0
- package/runtime/typed-route.mjs +19 -0
- package/runtime/watch-project.mjs +12 -5
- package/runtime/worker.mjs +36 -2
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Browser-only diagnostics. Records stay in this window, never sent to PRNext.
|
|
2
|
+
// Bound both retained entries and formatting work; do not invoke object getters.
|
|
3
|
+
const LIMIT = 500, TEXT_LIMIT = 128_000, TEXT_BUDGET = 2 * 1024 * 1024;
|
|
4
|
+
const clip = text => text.length > TEXT_LIMIT ? text.slice(0,TEXT_LIMIT) + '\n[Message truncated at 128000 characters]' : text;
|
|
5
|
+
const nativeStackGetter = Object.getOwnPropertyDescriptor(new Error(), 'stack')?.get;
|
|
6
|
+
export function formatConsoleValue(value, depth = 0, seen = new WeakSet()) {
|
|
7
|
+
try {
|
|
8
|
+
if (typeof value === 'string') return clip(value);
|
|
9
|
+
if (typeof value === 'function') return '[Function]';
|
|
10
|
+
if (value === null || typeof value !== 'object') return clip(String(value));
|
|
11
|
+
if (seen.has(value)) return '[Circular]';
|
|
12
|
+
seen.add(value);
|
|
13
|
+
if (value instanceof Error) {
|
|
14
|
+
// Custom error properties can also be getters. Do not evaluate them.
|
|
15
|
+
const stack = Object.getOwnPropertyDescriptor(value, 'stack'), message = Object.getOwnPropertyDescriptor(value, 'message');
|
|
16
|
+
// V8 exposes its lazy stack as a shared native getter. Permit that getter,
|
|
17
|
+
// but never a custom accessor installed on a logged error object.
|
|
18
|
+
const nativeStack = nativeStackGetter && stack?.get === nativeStackGetter ? Reflect.apply(nativeStackGetter, value, []) : undefined;
|
|
19
|
+
const text = typeof nativeStack === 'string' ? nativeStack : stack && 'value' in stack && typeof stack.value === 'string' ? stack.value : message && 'value' in message && typeof message.value === 'string' ? message.value : '[Error]';
|
|
20
|
+
return clip(text);
|
|
21
|
+
}
|
|
22
|
+
if (depth >= 3) return Array.isArray(value) ? '[Array]' : '[Object]';
|
|
23
|
+
const keys = Object.keys(value), parts = [];
|
|
24
|
+
let remaining = TEXT_LIMIT;
|
|
25
|
+
for (const key of keys.slice(0, 20)) {
|
|
26
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
27
|
+
const text = descriptor && 'value' in descriptor ? formatConsoleValue(descriptor.value, depth + 1, seen) : '[Getter]';
|
|
28
|
+
const part = (Array.isArray(value) ? '' : key.slice(0, 100) + ': ') + text;
|
|
29
|
+
parts.push(part.slice(0, remaining)); remaining -= part.length;
|
|
30
|
+
if (remaining <= 0) break;
|
|
31
|
+
}
|
|
32
|
+
if (keys.length > parts.length) parts.push('…');
|
|
33
|
+
return clip(Array.isArray(value) ? '[' + parts.join(', ') + ']' : '{' + parts.join(', ') + '}');
|
|
34
|
+
} catch { return '[Unavailable]'; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function formatArguments(args) {
|
|
38
|
+
const values = args.slice(0, 20);
|
|
39
|
+
if (typeof values[0] !== 'string') return clip(values.map(value => formatConsoleValue(value)).join(' '));
|
|
40
|
+
let index = 1;
|
|
41
|
+
const message = clip(values[0]).replace(/%[%sdifoOc]/g, token => {
|
|
42
|
+
if (token === '%%') return '%';
|
|
43
|
+
if (index >= values.length) return token;
|
|
44
|
+
const value = values[index++];
|
|
45
|
+
if (token === '%c') return '';
|
|
46
|
+
// Never coerce user objects through custom toString/valueOf methods.
|
|
47
|
+
if (/^%[dif]$/.test(token) && (typeof value === 'string' || typeof value === 'number')) return String(token === '%f' ? parseFloat(value) : parseInt(value, 10));
|
|
48
|
+
return formatConsoleValue(value);
|
|
49
|
+
});
|
|
50
|
+
return clip([message, ...values.slice(index).map(value => formatConsoleValue(value))].join(' '));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createConsoleCapture(target = globalThis) {
|
|
54
|
+
const entries = [], subscribers = new Set(), replacements = [];
|
|
55
|
+
let serial = 0, busy = false, scheduled = false, disposed = false, dropped = 0, retainedChars = 0;
|
|
56
|
+
function notify() {
|
|
57
|
+
if (scheduled || disposed) return;
|
|
58
|
+
scheduled = true;
|
|
59
|
+
queueMicrotask(() => {
|
|
60
|
+
scheduled = false;
|
|
61
|
+
if (!disposed) for (const subscriber of subscribers) { try { subscriber(); } catch {} }
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function record(level, args) {
|
|
65
|
+
if (busy || disposed) return;
|
|
66
|
+
busy = true;
|
|
67
|
+
try {
|
|
68
|
+
const text=formatArguments(args);entries.push({id:++serial,time:Date.now(),level,text});retainedChars+=text.length;
|
|
69
|
+
while(entries.length>LIMIT||retainedChars>TEXT_BUDGET){retainedChars-=entries.shift().text.length;dropped++;}
|
|
70
|
+
notify();
|
|
71
|
+
} catch {} finally { busy = false; }
|
|
72
|
+
}
|
|
73
|
+
function clear() { entries.length = 0; dropped = 0;retainedChars=0;notify(); }
|
|
74
|
+
for (const method of ['log', 'info', 'warn', 'error', 'debug', 'table', 'trace', 'assert', 'clear']) {
|
|
75
|
+
const original = target.console?.[method];
|
|
76
|
+
if (typeof original !== 'function') continue;
|
|
77
|
+
const replacement = function (...args) {
|
|
78
|
+
// Keep the native console, return value and exception behavior intact.
|
|
79
|
+
try { return Reflect.apply(original, target.console, args); }
|
|
80
|
+
finally {
|
|
81
|
+
if (method === 'clear') clear();
|
|
82
|
+
else if (method === 'assert') { if (!args[0]) record('error', ['Assertion failed:', ...args.slice(1)]); }
|
|
83
|
+
else record(method === 'table' || method === 'trace' ? 'log' : method, args);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
try { target.console[method] = replacement; replacements.push({method, original, replacement}); } catch {}
|
|
87
|
+
}
|
|
88
|
+
const onError = event => { if (event.error || event.message) record('error', [event.error || event.message]); };
|
|
89
|
+
const onRejection = event => record('error', ['Unhandled rejection:', event.reason]);
|
|
90
|
+
target.addEventListener('error', onError);
|
|
91
|
+
target.addEventListener('unhandledrejection', onRejection);
|
|
92
|
+
return {
|
|
93
|
+
snapshot: () => ({ entries: entries.slice(), dropped }), clear,
|
|
94
|
+
subscribe(callback) { subscribers.add(callback); return () => subscribers.delete(callback); },
|
|
95
|
+
dispose() {
|
|
96
|
+
disposed = true; subscribers.clear(); entries.length = 0;
|
|
97
|
+
for (const {method, original, replacement} of replacements) if (target.console[method] === replacement) target.console[method] = original;
|
|
98
|
+
target.removeEventListener('error', onError); target.removeEventListener('unhandledrejection', onRejection);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function isDevicePreview() {
|
|
104
|
+
try { return globalThis.frameElement?.getAttribute('data-prnext-preview') === 'true'; }
|
|
105
|
+
catch { return false; }
|
|
106
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Keep inherited-stdio RPC frames below the native listener's 64 KiB limit.
|
|
2
|
+
const FRAME_BYTES=48*1024,MAX_FILES=100000,MAX_RECORDS=50000,MAX_EDGES=1000000,MAX_BYTES=16*1024*1024;
|
|
3
|
+
function batches(values){
|
|
4
|
+
const result=[];let batch=[],bytes=2;
|
|
5
|
+
for(const value of values){const size=Buffer.byteLength(JSON.stringify(value))+1;if(size>FRAME_BYTES)throw Error('Development graph entry exceeds the control frame budget');if(bytes+size>FRAME_BYTES){result.push(batch);batch=[];bytes=2;}batch.push(value);bytes+=size;}
|
|
6
|
+
if(batch.length)result.push(batch);return result;
|
|
7
|
+
}
|
|
8
|
+
export function compactDependencyGraph(manifest){
|
|
9
|
+
if(manifest.devDependencyStatus?.complete===false)throw Error(manifest.devDependencyStatus.reason||'Incomplete development graph');
|
|
10
|
+
const files=[],ids=new Map(),records=[];let edges=0,bytes=0;
|
|
11
|
+
const intern=file=>{if(typeof file!=='string'||!file||Buffer.byteLength(file)>8192)throw Error('Invalid development dependency');if(!ids.has(file)){bytes+=Buffer.byteLength(file);ids.set(file,files.length);files.push(file);}return ids.get(file);};
|
|
12
|
+
for(const record of [...manifest.devDependencies||[],...manifest.devModules||[]]){
|
|
13
|
+
const entries=[...new Set(record.files)].map(intern);edges+=entries.length;bytes+=Buffer.byteLength(record.route)+entries.length*4;
|
|
14
|
+
// Split a single very broad route too; the native builder merges its parts.
|
|
15
|
+
for(let offset=0;offset<Math.max(entries.length,1);offset+=4000)records.push({route:record.route,module:!!record.module,files:entries.slice(offset,offset+4000)});
|
|
16
|
+
if(files.length>MAX_FILES||records.length>MAX_RECORDS||edges>MAX_EDGES||bytes>MAX_BYTES)throw Error('Development graph exceeds its bounded file, edge or memory budget');
|
|
17
|
+
}
|
|
18
|
+
return {files,records,edges};
|
|
19
|
+
}
|
|
20
|
+
export async function publishDependencyGraph(gateway,manifest){
|
|
21
|
+
const graph=compactDependencyGraph(manifest),transaction=String(manifest.buildId);
|
|
22
|
+
await gateway.request('graph-begin',{transaction,files:graph.files.length,records:graph.records.length,edges:graph.edges});
|
|
23
|
+
try{
|
|
24
|
+
for(const files of batches(graph.files))await gateway.request('graph-files',{transaction,files});
|
|
25
|
+
for(const records of batches(graph.records))await gateway.request('graph-records',{transaction,records});
|
|
26
|
+
return await gateway.request('graph-commit',{transaction});
|
|
27
|
+
}catch(error){await gateway.request('graph-abort',{transaction}).catch(()=>{});throw error;}
|
|
28
|
+
}
|
|
29
|
+
export async function affectedDependencies(gateway,files){
|
|
30
|
+
const routes=new Set(),modules=new Set(),unknown=new Set();let complete=true;
|
|
31
|
+
for(const batch of batches(files).flatMap(batch=>{const chunks=[];for(let i=0;i<batch.length;i+=512)chunks.push(batch.slice(i,i+512));return chunks;})){
|
|
32
|
+
const result=await gateway.request('affected',{files:batch,transitive:true});
|
|
33
|
+
complete&&=result.complete!==false;
|
|
34
|
+
for(const value of result.routes||[])routes.add(value);for(const value of result.modules||[])modules.add(value);for(const value of result.unknown||[])unknown.add(value);
|
|
35
|
+
}
|
|
36
|
+
// HMR compiler messages and frontend notifications also have finite budgets.
|
|
37
|
+
if(Buffer.byteLength(JSON.stringify([...routes]))>32*1024||Buffer.byteLength(JSON.stringify([...modules]))>256*1024)complete=false;
|
|
38
|
+
return {routes:complete?[...routes]:[],modules:complete?[...modules]:[],unknown:[...unknown],complete};
|
|
39
|
+
}
|
|
40
|
+
export const canSkipUnopenedRoutes=(result,files,dormant,indexValid)=>indexValid&&result.complete!==false&&files.length>0&&result.unknown.length===files.length&&result.unknown.every(file=>dormant.has(file));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// This chrome lives in its own shadow root and never changes application styles.
|
|
2
|
+
export const elementPickerStyle = `
|
|
3
|
+
:host{all:initial;position:fixed;inset:0;overflow:hidden;pointer-events:none;z-index:2147483647;color-scheme:dark;color:#ededed;font:12px/1.4 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;text-align:left;direction:ltr}
|
|
4
|
+
*,*::before,*::after{box-sizing:border-box}[hidden]{display:none!important}svg{display:block;width:16px;height:16px;flex:none}
|
|
5
|
+
.target{position:fixed;left:0;top:0;outline:1px dashed #e8e5ee;outline-offset:0;box-shadow:0 0 0 1px #17141b80;background:transparent;pointer-events:none}
|
|
6
|
+
.corner{position:absolute;width:5px;height:5px;background:#f4f2f8;box-shadow:0 0 0 1px #17141b;pointer-events:none}
|
|
7
|
+
.corner[data-corner=tl]{left:-3px;top:-3px}.corner[data-corner=t]{left:calc(50% - 2.5px);top:-3px}.corner[data-corner=tr]{right:-3px;top:-3px}.corner[data-corner=r]{right:-3px;top:calc(50% - 2.5px)}
|
|
8
|
+
.corner[data-corner=bl]{left:-3px;bottom:-3px}.corner[data-corner=b]{left:calc(50% - 2.5px);bottom:-3px}.corner[data-corner=br]{right:-3px;bottom:-3px}.corner[data-corner=l]{left:-3px;top:calc(50% - 2.5px)}
|
|
9
|
+
.picker-tag{position:fixed;left:0;top:0;width:max-content;max-width:min(280px,calc(100vw - 16px));padding:4px 6px;border-radius:5px;background:#101014ed;pointer-events:none}
|
|
10
|
+
.picker-tag-top{display:flex;align-items:center;min-width:0}
|
|
11
|
+
.picker-dimensions{color:#b6b2bd;font:10px/1.4 ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;font-variant-numeric:tabular-nums;flex:none}
|
|
12
|
+
.picker-source{display:flex;align-items:center;gap:6px;margin-top:4px;color:#f0edf5;font:500 11px/16px ui-monospace,SFMono-Regular,Consolas,monospace;white-space:nowrap;min-width:0}
|
|
13
|
+
.picker-source-name{min-width:0;overflow:hidden;text-overflow:ellipsis}.picker-source-line{flex:none;margin-left:-6px;color:#b6b2bd;font-variant-numeric:tabular-nums}
|
|
14
|
+
.picker-source-parent{flex:none;color:#a6a1ae;font:10px/16px ui-sans-serif,system-ui}
|
|
15
|
+
.picker-file-icon{display:grid;place-items:center;flex:none;width:15px;height:15px;border-radius:2px;color:#dad6e2;font:800 9px/1 ui-sans-serif,system-ui;letter-spacing:-.5px}.picker-file-icon svg{width:15px;height:15px}
|
|
16
|
+
.picker-file-icon[data-language=javascript]{background:#f7df1e;color:#17150b}.picker-file-icon[data-language=typescript]{background:#3178c6;color:#fff}.picker-file-icon[data-language=html]{background:#e34f26;color:#fff}.picker-file-icon[data-language=css]{background:#7956b5;color:#fff;font-size:12px}.picker-file-icon[data-language=json]{color:#f7df1e}.picker-file-icon[data-language=vue]{color:#60c59a}.picker-file-icon[data-language=svelte]{color:#ff7c55}.picker-file-icon[data-language=python]{color:#80b9ef}.picker-file-icon[data-language=rust]{color:#ebaa8e}
|
|
17
|
+
.hint{position:fixed;left:50%;bottom:max(16px,env(safe-area-inset-bottom));transform:translateX(-50%);display:flex;align-items:center;gap:10px;width:max-content;max-width:calc(100vw - 24px);padding:6px 6px 6px 14px;border:0;border-radius:22px;background:#080808;box-shadow:0 3px 8px #0006,0 12px 32px #0007;pointer-events:auto}
|
|
18
|
+
.picker-instruction{font-size:12px;font-weight:500;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
19
|
+
.picker-enter{margin-left:4px}.hint kbd{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 4px;border:1px solid #343238;border-radius:5px;color:#939098;background:transparent;font:10px/1 ui-sans-serif,system-ui}
|
|
20
|
+
.picker-cancel{display:flex;align-items:center;justify-content:center;gap:9px;min-height:34px;padding:6px 10px;border:0;border-radius:15px;background:#191919;color:#ededed;font:500 11px/1.4 ui-sans-serif,system-ui;cursor:pointer;white-space:nowrap;flex:none;-webkit-tap-highlight-color:transparent}
|
|
21
|
+
.picker-cancel:hover{background:#252525}.picker-cancel:focus-visible{outline:1px solid #aaa;outline-offset:-3px;background:#252525}
|
|
22
|
+
.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
23
|
+
@media(max-width:350px){.hint{gap:8px}.hint .picker-enter{display:none}.picker-tag{max-width:calc(100vw - 16px)}}
|
|
24
|
+
@media(pointer:coarse){.picker-cancel{min-width:64px;min-height:44px;padding-inline:12px}.picker-cancel kbd,.hint .picker-enter{display:none}.hint{border-radius:28px}.picker-cancel{border-radius:21px}}
|
|
25
|
+
@media(prefers-reduced-motion:no-preference){.picker-cancel{transition:background-color 120ms}.target,.picker-tag{animation:picker-mark-in 110ms ease-out}.target[data-motion=true]{transition:translate 110ms cubic-bezier(.22,.8,.2,1),width 110ms cubic-bezier(.22,.8,.2,1),height 110ms cubic-bezier(.22,.8,.2,1)}.picker-tag[data-motion=true]{transition:translate 110ms cubic-bezier(.22,.8,.2,1)}.hint{animation:picker-bar-in 180ms cubic-bezier(.16,1,.3,1)}@keyframes picker-mark-in{from{opacity:0}to{opacity:1}}@keyframes picker-bar-in{from{opacity:0;translate:0 6px}to{opacity:1;translate:0 0}}}
|
|
26
|
+
@media(forced-colors:active){.target{outline-color:Highlight;box-shadow:none;background:transparent}.corner{background:Highlight;box-shadow:0 0 0 1px Canvas}.picker-tag,.hint{background:Canvas;color:CanvasText;border:1px solid ButtonText;box-shadow:none}.picker-dimensions,.picker-source,.picker-source-line,.picker-source-parent{color:CanvasText}.picker-file-icon[data-language]{color:CanvasText;background:Canvas}.picker-cancel{background:ButtonFace;color:ButtonText;border:1px solid ButtonText}.picker-cancel:focus-visible{outline:2px solid Highlight;outline-offset:-3px}.hint kbd{color:ButtonText;border-color:ButtonText}}
|
|
27
|
+
`;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { elementPickerStyle } from './dev-element-picker-style.mjs';
|
|
2
|
+
|
|
3
|
+
const PROPERTIES = ['display', 'position', 'width', 'height', 'color', 'background-color', 'font-family', 'font-size', 'font-weight', 'line-height', 'padding', 'margin', 'gap', 'border-radius', 'align-items', 'justify-content', 'grid-template-columns'];
|
|
4
|
+
const HOVER_HOLD = 35, SMALL_HOVER_HOLD = 75, POINTER_REST = 50;
|
|
5
|
+
const pickerEpochs=new WeakMap();
|
|
6
|
+
const toolNode = node => {
|
|
7
|
+
for(let current=node;current;current=current.getRootNode?.().host)
|
|
8
|
+
if(current.closest?.('prnext-dev-tools,prnext-dev-picker,#__prnext_dev_error__'))return true;
|
|
9
|
+
return false;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function sourceLocation(element) {
|
|
13
|
+
for(let owner=element;owner;owner=owner.parentElement||owner.getRootNode?.().host) {
|
|
14
|
+
const source=owner.getAttribute?.('data-prnext-source');
|
|
15
|
+
if(source)return {source,sourceRelation:owner===element?'element':'ancestor'};
|
|
16
|
+
}
|
|
17
|
+
return {source:null,sourceRelation:null};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function describeElement(element, view = globalThis) {
|
|
21
|
+
const bounds = element.getBoundingClientRect(), style = view.getComputedStyle(element);
|
|
22
|
+
const {source,sourceRelation}=sourceLocation(element);
|
|
23
|
+
const tag = element.tagName.toLowerCase(), selector = element.id ? '#' + view.CSS.escape(element.id) : tag + [...element.classList].slice(0, 3).map(name => '.' + view.CSS.escape(name)).join('');
|
|
24
|
+
let text = '';
|
|
25
|
+
if (['input', 'textarea', 'select'].includes(tag)) text = '[Form value omitted]';
|
|
26
|
+
else {
|
|
27
|
+
const walker = view.document.createTreeWalker(element, view.NodeFilter.SHOW_TEXT);
|
|
28
|
+
while (walker.nextNode() && text.length < 4000) {
|
|
29
|
+
if (!walker.currentNode.parentElement?.closest('input,textarea,select,script,style,[hidden],[aria-hidden="true"]')) text += walker.currentNode.textContent + ' ';
|
|
30
|
+
}
|
|
31
|
+
text = text.trim().replace(/\s+/g, ' ').slice(0, 4000);
|
|
32
|
+
}
|
|
33
|
+
return { kind: 'element', path: view.location.pathname, selector, tag, text, source, sourceRelation,
|
|
34
|
+
bounds: { x: Math.round(bounds.x), y: Math.round(bounds.y), width: Math.round(bounds.width), height: Math.round(bounds.height) },
|
|
35
|
+
styles: Object.fromEntries(PROPERTIES.map(property => [property, style.getPropertyValue(property)])) };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// User-triggered capture only. Form controls, scripts, frames and development UI
|
|
39
|
+
// never enter an image; a CORS/CSP failure is reported rather than hidden.
|
|
40
|
+
export async function captureElementImage(element) {
|
|
41
|
+
const { toPng } = await import('html-to-image');
|
|
42
|
+
const bounds = element.getBoundingClientRect();
|
|
43
|
+
if (!bounds.width || !bounds.height) throw Error('The element has no visible size.');
|
|
44
|
+
if (['INPUT', 'TEXTAREA', 'SELECT', 'IFRAME', 'VIDEO'].includes(element.tagName)) throw Error('Form values and embedded content are excluded from images.');
|
|
45
|
+
const scale = Math.min(1, 1400 / bounds.width, 1000 / bounds.height);
|
|
46
|
+
const result = await toPng(element, { pixelRatio: scale, skipFonts: true, cacheBust: false,
|
|
47
|
+
filter: node => !toolNode(node) && !['SCRIPT', 'IFRAME', 'INPUT', 'TEXTAREA', 'SELECT', 'VIDEO', 'AUDIO'].includes(node.tagName),
|
|
48
|
+
});
|
|
49
|
+
if (result.length > 349525) throw Error('The image exceeds the 256 KB context limit. Select a smaller element.');
|
|
50
|
+
return { mimeType: 'image/png', data: result.slice(result.indexOf(',') + 1), width: Math.round(bounds.width * scale), height: Math.round(bounds.height * scale) };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function createElementPicker({ tools, onSelect, onCancel = () => {}, view = globalThis }) {
|
|
54
|
+
const document = view.document, controller = new AbortController(), options = { signal: controller.signal, capture: true };
|
|
55
|
+
const epoch=(pickerEpochs.get(tools)||0)+1;pickerEpochs.set(tools,epoch);
|
|
56
|
+
const host = document.createElement('prnext-dev-picker'), shadow = host.attachShadow({ mode: 'open' });
|
|
57
|
+
const style = document.createElement('style'); style.textContent=elementPickerStyle;
|
|
58
|
+
const node=(tag,className,text)=>{const result=document.createElement(tag);if(className)result.className=className;if(text!==undefined)result.textContent=text;return result;};
|
|
59
|
+
const icon=paths=>`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${paths}</svg>`;
|
|
60
|
+
const box = node('div','target'); box.hidden = true; box.setAttribute('aria-hidden','true');
|
|
61
|
+
for(const corner of ['tl','t','tr','r','br','b','bl','l']){const mark=node('span','corner');mark.dataset.corner=corner;box.append(mark);}
|
|
62
|
+
const tag=node('div','picker-tag');tag.hidden=true;tag.setAttribute('aria-hidden','true');
|
|
63
|
+
const tagTop=node('div','picker-tag-top'),dimensions=node('span','picker-dimensions'),source=node('span','picker-source');
|
|
64
|
+
const fileIcon=node('span','picker-file-icon'),fileName=node('span','picker-source-name'),fileLine=node('span','picker-source-line'),parentSource=node('span','picker-source-parent','Parent');
|
|
65
|
+
source.append(fileIcon,fileName,fileLine,parentSource);tagTop.append(dimensions);tag.append(tagTop,source);
|
|
66
|
+
const fileTypes={js:['javascript','JS'],jsx:['javascript','JS'],mjs:['javascript','JS'],cjs:['javascript','JS'],ts:['typescript','TS'],tsx:['typescript','TS'],mts:['typescript','TS'],cts:['typescript','TS'],html:['html','<>'],htm:['html','<>'],css:['css','#'],scss:['css','#'],json:['json','{}'],vue:['vue','V'],svelte:['svelte','S'],md:['markdown','M'],mdx:['markdown','M'],py:['python','Py'],rs:['rust','Rs']};
|
|
67
|
+
let fileIconKey;
|
|
68
|
+
function updateFileIcon(filename) {
|
|
69
|
+
const extension=filename?.split('.').at(-1)?.toLowerCase(),type=Object.hasOwn(fileTypes,extension)?fileTypes[extension]:null,key=type?.[0]||'file';
|
|
70
|
+
if(fileIconKey===key)return;fileIconKey=key;fileIcon.dataset.language=key;
|
|
71
|
+
if(type){fileIcon.textContent=type[1];fileIcon.title=key;}
|
|
72
|
+
else{fileIcon.innerHTML=icon('<path d="M14 3H6a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8Z"/><path d="M14 3v5h5"/>');fileIcon.title='File';}
|
|
73
|
+
}
|
|
74
|
+
const hint=node('div','hint');hint.setAttribute('role','region');hint.setAttribute('aria-label','Element picker');
|
|
75
|
+
const instruction=node('span','picker-instruction','Pick an element'),enter=node('kbd','picker-enter','↵');enter.setAttribute('aria-hidden','true');
|
|
76
|
+
const cancelButton=node('button','picker-cancel','Cancel');cancelButton.type='button';cancelButton.setAttribute('aria-label','Cancel');
|
|
77
|
+
const escape=node('kbd','','esc');escape.setAttribute('aria-hidden','true');cancelButton.append(escape);cancelButton.onclick=()=>cancel();
|
|
78
|
+
const live=node('span','sr-only');live.setAttribute('role','status');
|
|
79
|
+
hint.append(instruction,enter,cancelButton);shadow.append(style,box,tag,hint,live);document.documentElement.append(host);
|
|
80
|
+
const previous = document.activeElement; let active, frame, pending, hoverTimer, pointer, stopped = false, viewportKey, placed = false;
|
|
81
|
+
const viewport=()=>({left:view.visualViewport?.offsetLeft||0,top:view.visualViewport?.offsetTop||0,width:view.visualViewport?.width||view.innerWidth,height:view.visualViewport?.height||view.innerHeight});
|
|
82
|
+
function placeHint() {
|
|
83
|
+
const visible=viewport(),key=[visible.left,visible.top,visible.width,visible.height].join(':');
|
|
84
|
+
if(viewportKey!==key){viewportKey=key;hint.style.maxWidth=Math.max(1,visible.width-24)+'px';}
|
|
85
|
+
const height=hint.offsetHeight,width=hint.offsetWidth;let top=Math.max(visible.top+8,visible.top+visible.height-height-16),left=visible.left+(visible.width-width)/2;
|
|
86
|
+
const hud=document.querySelector('prnext-dev-tools')?.getBoundingClientRect();
|
|
87
|
+
if(hud?.width&&left<hud.right&&left+width>hud.left&&top<hud.bottom&&top+height>hud.top){
|
|
88
|
+
const candidates=[hud.right+8,hud.left-width-8],fitting=candidates.find(candidate=>candidate>=visible.left+12&&candidate+width<=visible.left+visible.width-12);
|
|
89
|
+
if(fitting!==undefined)left=fitting;else top=Math.max(visible.top+8,hud.top-height-12);
|
|
90
|
+
}
|
|
91
|
+
hint.style.left=(left+width/2)+'px';hint.style.bottom='auto';hint.style.top=top+'px';
|
|
92
|
+
return {visible,hint:{top,height}};
|
|
93
|
+
}
|
|
94
|
+
function placeTag(rect, animate) {
|
|
95
|
+
const {visible,hint:bar}=placeHint(),margin=8,gap=9;
|
|
96
|
+
tag.style.maxWidth=Math.max(1,Math.min(280,visible.width-margin*2))+'px';
|
|
97
|
+
const size=tag.getBoundingClientRect(),minX=visible.left+margin,maxX=visible.left+visible.width-size.width-margin;
|
|
98
|
+
const maxY=Math.min(visible.top+visible.height-size.height-margin,bar.top-size.height-margin),minY=visible.top+margin;
|
|
99
|
+
if(maxY<minY){tag.hidden=true;return;}
|
|
100
|
+
const above=rect.top-size.height-gap,below=rect.bottom+gap;
|
|
101
|
+
const top=above>=minY&&above<=maxY?above:below>=minY&&below<=maxY?below:Math.min(maxY,Math.max(minY,above));
|
|
102
|
+
const left=Math.min(Math.max(minX,maxX),Math.max(minX,rect.left));
|
|
103
|
+
// A wider filename must not travel outside the viewport from its old anchor.
|
|
104
|
+
// Otherwise CSS can retarget the current animation without restarting it.
|
|
105
|
+
tag.dataset.motion=String(animate&&size.left>=minX&&size.left<=maxX&&size.top>=minY&&size.top<=maxY);
|
|
106
|
+
tag.style.translate=left+'px '+top+'px';tag.dataset.side=top+size.height<=rect.top?'above':'below';
|
|
107
|
+
}
|
|
108
|
+
function highlight(element, keyboard=false, animate=true) {
|
|
109
|
+
const node=element;
|
|
110
|
+
if (!node || toolNode(node) || node === document.documentElement || node === document.body) return;
|
|
111
|
+
active = node; const rect = node.getBoundingClientRect();
|
|
112
|
+
if(!rect.width||!rect.height){box.hidden=true;tag.hidden=true;placed=false;return;}
|
|
113
|
+
const moving=animate&&placed;
|
|
114
|
+
box.dataset.motion=String(moving);box.hidden = false; Object.assign(box.style, { translate: rect.left+'px '+rect.top+'px', width: rect.width+'px', height: rect.height+'px' });
|
|
115
|
+
const location=sourceLocation(node),parsed=location.source?.match(/^(.*?):(\d+)(?::(\d+))?$/),filename=parsed?.[1].replaceAll('\\','/').split('/').at(-1);
|
|
116
|
+
dimensions.textContent=Math.round(rect.width)+' × '+Math.round(rect.height);
|
|
117
|
+
updateFileIcon(filename);fileName.textContent=filename||location.source||'<'+node.tagName.toLowerCase()+'>';
|
|
118
|
+
fileLine.textContent=parsed?':'+parsed[2]:'';fileLine.hidden=!parsed;parentSource.hidden=location.sourceRelation!=='ancestor';
|
|
119
|
+
source.title=location.source?(location.sourceRelation==='ancestor'?'Parent source: ':'')+location.source:'Source location unavailable';source.dataset.ancestor=String(location.sourceRelation==='ancestor');
|
|
120
|
+
tag.hidden=false;placeTag(rect,moving);
|
|
121
|
+
// Commit the first rectangle in place; later targets interpolate from it.
|
|
122
|
+
// The existing layout read in placeTag also commits the frame's geometry.
|
|
123
|
+
placed=true;
|
|
124
|
+
if(keyboard)live.textContent=node.tagName.toLowerCase()+', '+Math.round(rect.width)+' by '+Math.round(rect.height)+' pixels'+(filename?', '+(location.sourceRelation==='ancestor'?'parent source ':'source ')+filename+', line '+parsed[2]:'')+'. Enter selects.';
|
|
125
|
+
}
|
|
126
|
+
function clearHover(resetPointer=true) {
|
|
127
|
+
view.clearTimeout(hoverTimer);view.cancelAnimationFrame(frame);
|
|
128
|
+
hoverTimer=undefined;frame=undefined;pending=undefined;if(resetPointer)pointer=undefined;
|
|
129
|
+
}
|
|
130
|
+
function hoverReadyAt() {
|
|
131
|
+
const minimum=pending.since+pending.hold;
|
|
132
|
+
const slow=pending.slowSince===undefined?Infinity:Math.max(minimum,pending.slowSince+pending.hold);
|
|
133
|
+
return Math.min(slow,Math.max(minimum,pointer.lastMove+POINTER_REST));
|
|
134
|
+
}
|
|
135
|
+
function queueHover() {
|
|
136
|
+
view.clearTimeout(hoverTimer);hoverTimer=undefined;
|
|
137
|
+
if(stopped||!pending)return;
|
|
138
|
+
const remaining=hoverReadyAt()-view.performance.now();
|
|
139
|
+
if(frame!==undefined){if(remaining<=0)return;view.cancelAnimationFrame(frame);frame=undefined;}
|
|
140
|
+
if(remaining>0){hoverTimer=view.setTimeout(queueHover,remaining);return;}
|
|
141
|
+
frame=view.requestAnimationFrame(()=>{
|
|
142
|
+
frame=undefined;
|
|
143
|
+
// Acceleration between the timer and paint must not commit a stale hover.
|
|
144
|
+
if(stopped||!pending)return;
|
|
145
|
+
if(hoverReadyAt()>view.performance.now()){queueHover();return;}
|
|
146
|
+
const candidate=pending.node;pending=undefined;
|
|
147
|
+
if(candidate.isConnected)highlight(candidate);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function hover(node,event) {
|
|
151
|
+
if(!node||toolNode(node)||node===document.documentElement||node===document.body){clearHover();return;}
|
|
152
|
+
const now=view.performance.now(),x=event.clientX,y=event.clientY;
|
|
153
|
+
if(!pointer)pointer={x,y,time:now,lastMove:now,speed:0,instant:0};
|
|
154
|
+
else{
|
|
155
|
+
const distance=Math.hypot(x-pointer.x,y-pointer.y),elapsed=now-pointer.time;
|
|
156
|
+
// Compare with the last meaningful position so subpixel steps accumulate.
|
|
157
|
+
if(distance>1){
|
|
158
|
+
const instant=distance/Math.max(1,Math.min(elapsed,64));
|
|
159
|
+
pointer={x,y,time:now,lastMove:now,instant,speed:elapsed>120?instant:pointer.speed*.35+instant*.65};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if(node===active){
|
|
163
|
+
clearHover(false);frame=view.requestAnimationFrame(()=>{frame=undefined;if(!stopped&&node.isConnected)highlight(node);});return;
|
|
164
|
+
}
|
|
165
|
+
if(node!==pending?.node){
|
|
166
|
+
clearHover(false);const rect=node.getBoundingClientRect();
|
|
167
|
+
pending={node,since:now,hold:Math.min(rect.width,rect.height)<28||active?.contains(node)?SMALL_HOVER_HOLD:HOVER_HOLD};
|
|
168
|
+
}
|
|
169
|
+
// Hysteresis accepts deliberate slow movement, while a sudden acceleration
|
|
170
|
+
// cancels that decision. A short pause also confirms intent after a fast move.
|
|
171
|
+
if(pointer.instant>=.85||pointer.speed>=.65)pending.slowSince=undefined;
|
|
172
|
+
else if(pointer.speed<=.45&&pending.slowSince===undefined)pending.slowSince=now;
|
|
173
|
+
queueHover();
|
|
174
|
+
}
|
|
175
|
+
function stop(restore = true) { if (stopped) return; stopped = true; controller.abort(); clearHover(); host.remove(); if (restore && previous?.isConnected) previous.focus({ preventScroll: true }); }
|
|
176
|
+
function cancel(){if(stopped)return;stop();onCancel();}
|
|
177
|
+
async function select(node) {
|
|
178
|
+
if (!node || toolNode(node)) return;
|
|
179
|
+
const context = describeElement(node, view); stop(false); onSelect(context);
|
|
180
|
+
let expectedContext,knownContext=false;
|
|
181
|
+
try{const state=tools.snapshot?.();knownContext=Boolean(state&&Object.hasOwn(state,'context'));expectedContext=state?.context;}catch{}
|
|
182
|
+
const image = captureElementImage(node).then(screenshot => ({ screenshot })).catch(error => ({ screenshot: null, screenshotError: error.message }));
|
|
183
|
+
const mapped = context.source ? tools.call('dev_source', { source: context.source }).catch(error => ({ mapped: false, error: error.message })) : Promise.resolve({ mapped: false, error: 'No source location was emitted for this element.' });
|
|
184
|
+
const [capture, source] = await Promise.all([image, mapped]);
|
|
185
|
+
if(pickerEpochs.get(tools)!==epoch)return;
|
|
186
|
+
if(knownContext){try{if(tools.snapshot().context!==expectedContext)return;}catch{return;}}
|
|
187
|
+
const complete = { ...context, ...capture, mappedSource: source }; tools.setContext(complete); void tools.browser('selection', { selection: complete }); onSelect(complete);
|
|
188
|
+
}
|
|
189
|
+
document.addEventListener('pointermove', event => {
|
|
190
|
+
hover(event.composedPath().find(item => item instanceof view.Element),event);
|
|
191
|
+
}, options);
|
|
192
|
+
document.addEventListener('pointerout',event=>{if(!event.relatedTarget)clearHover();},options);
|
|
193
|
+
document.addEventListener('pointercancel',clearHover,options);view.addEventListener('blur',clearHover,options);
|
|
194
|
+
document.addEventListener('click', event => {
|
|
195
|
+
const node = event.composedPath().find(item => item instanceof view.Element); if (toolNode(node)) return;
|
|
196
|
+
event.preventDefault(); event.stopImmediatePropagation(); void select(node);
|
|
197
|
+
}, options);
|
|
198
|
+
document.addEventListener('keydown', event => {
|
|
199
|
+
if (event.key === 'Escape') { event.preventDefault(); event.stopImmediatePropagation(); cancel(); }
|
|
200
|
+
else if (event.key === 'Enter' && active && !toolNode(event.composedPath().find(item=>item instanceof view.Element))) { event.preventDefault(); event.stopImmediatePropagation(); void select(active); }
|
|
201
|
+
}, options);
|
|
202
|
+
document.addEventListener('focusin', event => {clearHover();highlight(event.composedPath().find(item=>item instanceof view.Element),true);}, options);
|
|
203
|
+
// Scrolling and resizing keep the outline attached, without a trailing frame.
|
|
204
|
+
const reposition=()=>{clearHover();placeHint();if(active)highlight(active,false,false);};
|
|
205
|
+
view.addEventListener('scroll',reposition,options);view.addEventListener('resize',reposition,options);
|
|
206
|
+
view.visualViewport?.addEventListener('scroll',reposition,options);view.visualViewport?.addEventListener('resize',reposition,options);
|
|
207
|
+
placeHint();queueMicrotask(()=>{if(!stopped)live.textContent='Pick an element. Click, or use Tab and Enter. Escape or Cancel exits.';});
|
|
208
|
+
// Leave the inert logo menu's tab sequence. The next Tab starts with the
|
|
209
|
+
// application's first control rather than cycling back through tool buttons.
|
|
210
|
+
const tabindex=document.body.getAttribute('tabindex');document.body.tabIndex=-1;document.body.focus({preventScroll:true});
|
|
211
|
+
if(tabindex===null)document.body.removeAttribute('tabindex');else document.body.setAttribute('tabindex',tabindex);
|
|
212
|
+
return { dispose: stop };
|
|
213
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Development-only error dialog; application styles never enter this shadow root.
|
|
2
|
+
export const devErrorOverlayStyle = `
|
|
3
|
+
:host{all:initial;position:fixed;inset:0;z-index:2147483647;background:#080808;color-scheme:dark;color:#ededed;font:13px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;text-align:left;direction:ltr;-webkit-font-smoothing:antialiased}
|
|
4
|
+
*,*::before,*::after{box-sizing:border-box}[hidden]{display:none!important}
|
|
5
|
+
dialog{position:fixed;inset:0;margin:auto;width:min(960px,calc(100vw - 48px));height:fit-content;max-width:none;max-height:calc(100dvh - 48px);padding:0;border:0;border-radius:14px;background:#080808;color:#ededed;box-shadow:0 0 0 1px #242424;overflow:hidden;overscroll-behavior:contain;font:inherit;text-align:left}
|
|
6
|
+
dialog[open]{display:flex;flex-direction:column}dialog::backdrop{background:#080808}
|
|
7
|
+
.error-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px 16px;padding:14px 20px;border-bottom:1px solid #202020;flex:none}
|
|
8
|
+
.error-brand{display:flex;align-items:center;gap:8px;min-width:0;color:#aaa;font-size:12px}.error-brand strong{color:#ededed;font-weight:600}.error-brand-label::before{content:'/';margin-right:8px;color:#555}.error-count{min-width:20px;padding:1px 5px;border-radius:5px;background:#1b1b1b;color:#bbb;text-align:center;font:11px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}
|
|
9
|
+
.error-header-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:6px;margin-left:auto;max-width:100%}.icon{display:inline-flex;flex:none}.icon svg{display:block;width:14px;height:14px;flex:none}
|
|
10
|
+
button,a{display:inline-flex;align-items:center;justify-content:center;gap:7px;min-height:36px;padding:7px 10px;border:1px solid #303030;border-radius:8px;background:#111;color:#ddd;font:500 12px/1.5 ui-sans-serif,system-ui;white-space:nowrap;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent}
|
|
11
|
+
button:hover,a:hover{background:#1a1a1a;color:#fff}button:focus-visible,a:focus-visible,summary:focus-visible,[tabindex]:focus-visible{outline:none;background:#242424;color:#fff;box-shadow:inset 0 0 0 1px #555}
|
|
12
|
+
dialog[open]:focus,dialog[open]:focus-visible{outline:none}.error-ai:focus-visible{background:#fff;color:#080808;box-shadow:inset 0 0 0 1px #777}
|
|
13
|
+
.error-dismiss{flex:none;width:36px;padding:0;background:transparent;border-color:transparent;color:#999}.error-dismiss .icon svg{width:16px;height:16px}.error-copy{min-width:108px}.error-copy[data-status=copied]{color:#a8dabd}.error-copy[data-status=error]{color:#f39199}
|
|
14
|
+
.error-ai{background:#e5e5e5;border-color:transparent;color:#111}.error-ai:hover{background:#fff;color:#080808}
|
|
15
|
+
.error-issue-region{flex:none;min-width:0;padding:8px 12px;border-bottom:1px solid #202020}.error-list{max-height:min(216px,28dvh);overflow:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:#333 #080808}
|
|
16
|
+
.error-row{display:grid;grid-template-columns:126px minmax(0,1fr) 8px;align-items:center;gap:3px 12px;width:100%;min-height:40px;padding:10px 12px;border:0;border-radius:7px;background:transparent;text-align:left;font-weight:400;white-space:normal}
|
|
17
|
+
.error-row:hover{background:#111}.error-row[aria-pressed=true]{background:#191919;color:#eee}.error-row-type{display:flex;align-items:center;gap:7px;min-width:0;color:#aaa;font-size:11px;white-space:nowrap}.error-row-dot{width:5px;height:5px;border-radius:50%;background:#eb707b;flex:none}.error-row[data-recoverable=true] .error-row-dot{background:transparent;box-shadow:inset 0 0 0 1px #eb707b}
|
|
18
|
+
.error-row-message{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#c6c6c6;font-size:12px}.error-row-file{grid-column:2;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#888;font:10px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace}.error-list-note{margin:6px 12px 2px;color:#888;font-size:11px}
|
|
19
|
+
.error-row[data-kind=hydration]{grid-template-columns:minmax(0,1fr) 8px}.error-row[data-kind=hydration] .error-row-type{font-size:12px;color:#ddd}.error-row[data-kind=hydration] .error-row-file{grid-column:1}.error-row[aria-expanded=false] .error-row-file{display:none}.error-row:focus-visible{background:#242424;box-shadow:inset 0 0 0 1px #555}
|
|
20
|
+
.error-row-chevron{grid-column:-2;grid-row:1;justify-self:end;width:6px;height:6px;border-right:1px solid #888;border-bottom:1px solid #888;rotate:45deg;translate:0 -2px}.error-row[aria-expanded=true] .error-row-chevron{rotate:225deg;translate:0 1px}
|
|
21
|
+
.error-body{min-height:0;overflow:auto;overscroll-behavior:contain;padding:20px;scrollbar-width:thin;scrollbar-color:#333 #080808}
|
|
22
|
+
.error-intro{margin-bottom:18px}.error-intro h1{margin:0;color:#f0f0f0;font-size:20px;line-height:1.3;letter-spacing:-.025em;font-weight:600;text-wrap:balance}
|
|
23
|
+
.error-title-toggle{justify-content:flex-start;gap:12px;max-width:100%;min-height:32px;padding:2px 0;border:0;border-radius:4px;background:transparent;color:inherit;font:inherit;letter-spacing:inherit;white-space:normal;text-align:left}.error-title-toggle::after{content:'';width:7px;height:7px;flex:none;border-right:1px solid #888;border-bottom:1px solid #888;rotate:225deg;translate:0 2px}.error-title-toggle:hover{background:transparent;color:#fff}
|
|
24
|
+
.error-summary{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden;max-width:70ch;margin:9px 0 0;color:#b8b8b8;font-size:13px;line-height:1.6;overflow-wrap:anywhere;text-wrap:pretty}
|
|
25
|
+
.error-editor-actions{display:flex;justify-content:flex-end;margin:-6px 0 18px}.error-editor-actions a{min-height:32px;padding:5px 8px;font-size:11px;background:transparent;color:#aaa;border-color:transparent}.error-editor-actions a:hover{color:#eee;background:#191919}
|
|
26
|
+
.source{margin:0 0 18px;min-width:0;border-radius:11px;background:#111;overflow:hidden;box-shadow:inset 0 0 0 1px #ffffff08}
|
|
27
|
+
.source-head{display:flex;align-items:center;gap:8px;min-width:0;padding:11px 16px;border-bottom:1px solid #222;color:#bbb}.source-head svg{width:14px;height:14px;flex:none;color:#888}.location{min-width:0;overflow-wrap:anywhere;font:12px/1.5 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
28
|
+
.source pre{max-height:310px;margin:0;padding:14px 16px;overflow:auto;overscroll-behavior:contain;white-space:pre;overflow-wrap:normal;tab-size:2;color:#d4d4d4;font:12px/1.7 ui-monospace,SFMono-Regular,Consolas,monospace;scrollbar-width:thin;scrollbar-color:#3b3b3b #111}
|
|
29
|
+
.source pre:focus-visible{outline-offset:-2px}
|
|
30
|
+
.error-disclosures{border-top:1px solid #202020}.error-disclosure+.error-disclosure{border-top:1px solid #202020}
|
|
31
|
+
summary{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:40px;padding:10px 2px;color:#aaa;font-size:12px;list-style:none;cursor:pointer}
|
|
32
|
+
summary::-webkit-details-marker{display:none}summary::after{content:'';width:6px;height:6px;flex:none;margin-right:4px;border-right:1px solid currentColor;border-bottom:1px solid currentColor;rotate:45deg;translate:0 -2px}details[open]>summary::after{rotate:225deg;translate:0 1px}summary:hover{color:#eee}
|
|
33
|
+
.error-full{max-height:min(260px,35dvh);margin:0 0 14px;padding:14px 16px;border-radius:8px;background:#111;color:#bbb;overflow:auto;overscroll-behavior:contain;white-space:pre-wrap;overflow-wrap:anywhere;font:13px/1.65 ui-sans-serif,system-ui;tab-size:2;scrollbar-width:thin;scrollbar-color:#333 #111}
|
|
34
|
+
.error-stack{white-space:pre;overflow-wrap:normal;font:11px/1.7 ui-monospace,SFMono-Regular,Consolas,monospace}
|
|
35
|
+
.error-full:focus-visible{outline-offset:-2px}.error-source-status{margin:14px 0 0;color:#888;font-size:12px;line-height:1.5;overflow-wrap:anywhere}.error-source-status:empty{display:none}
|
|
36
|
+
.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
37
|
+
@media(max-width:600px){dialog{width:calc(100vw - 16px);max-height:calc(100dvh - 24px);border-radius:12px}.error-header{padding:12px 16px}.error-header-actions{flex:1 1 100%;justify-content:flex-start}.error-header-actions .error-dismiss{margin-left:auto}.error-issue-region{padding:7px 8px}.error-row{grid-template-columns:minmax(0,1fr) 8px;gap:3px;padding:9px 10px}.error-row-message,.error-row-file{grid-column:1}.error-list-note{margin-inline:10px}.error-body{padding:18px 16px}.error-intro h1{font-size:18px}.source-head{padding:10px 12px}.source pre{padding:12px;max-height:260px;font-size:11px}.error-full{padding:12px}}
|
|
38
|
+
@media(pointer:coarse){button,a,summary,.error-editor-actions a,.error-row,.error-title-toggle{min-height:44px}.error-dismiss{width:44px}}
|
|
39
|
+
@media(prefers-reduced-motion:no-preference){button:not(.error-row),a,summary{transition:background-color 120ms ease-out,color 120ms ease-out}}
|
|
40
|
+
@media(forced-colors:active){:host,dialog,dialog::backdrop{background:Canvas;color:CanvasText}dialog{border:1px solid CanvasText}.error-header,.error-issue-region,.error-disclosures,.error-disclosure+.error-disclosure,.source-head{border-color:CanvasText}.error-intro h1,.error-brand,.error-brand strong,.error-count,.error-summary,.error-row-type,.error-row-message,.error-row-file,.error-list-note,summary,.error-source-status,.location{color:CanvasText}.error-count,.error-row[aria-pressed=true]{background:Canvas}.error-row[aria-pressed=true]{outline:1px solid Highlight;outline-offset:-1px}.error-row-dot,.error-row[data-recoverable=true] .error-row-dot{background:CanvasText;box-shadow:none}.source,.error-full{background:Canvas;color:CanvasText;border:1px solid CanvasText;box-shadow:none}.source pre{color:CanvasText}.error-ai,button,a{background:ButtonFace;color:ButtonText;border-color:ButtonText}button:focus-visible,a:focus-visible,summary:focus-visible,[tabindex]:focus-visible{outline:1px solid Highlight;outline-offset:-2px;box-shadow:none}}
|
|
41
|
+
`;
|