enigma-cli 1.43.0 → 1.44.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/assets/registry/primitives/registry.json +498 -8
- package/assets/registry/utils/registry.json +4 -2
- package/assets/skills/anti-overengineering-policy/skill.json +1 -1
- package/assets/skills/anti-overengineering-review/skill.json +1 -1
- package/assets/skills/backend-policy/skill.json +1 -1
- package/assets/skills/ciphera-style-policy/skill.json +1 -1
- package/assets/skills/code-review-policy/skill.json +1 -1
- package/assets/skills/core-engineering-policy/skill.json +1 -1
- package/assets/skills/database-expert/skill.json +1 -1
- package/assets/skills/debugging-policy/skill.json +1 -1
- package/assets/skills/dependency-policy/skill.json +1 -1
- package/assets/skills/email-policy/skill.json +1 -1
- package/assets/skills/frontend-design/skill.json +1 -1
- package/assets/skills/frontend-policy/skill.json +1 -1
- package/assets/skills/git-policy/skill.json +1 -1
- package/assets/skills/logo-sourcing-policy/skill.json +1 -1
- package/assets/skills/security-policy/skill.json +1 -1
- package/assets/skills/skill-creator/skill.json +1 -1
- package/assets/skills/task-completion-policy/skill.json +1 -1
- package/assets/skills/technical-writing-policy/skill.json +1 -1
- package/assets/skills/testing-policy/skill.json +1 -1
- package/assets/skills/validation-policy/skill.json +1 -1
- package/bin/checksums.json +4 -4
- package/bin/enigma.mjs +34 -0
- package/dist/codegraph-hook.js +2483 -0
- package/dist/guardrails.js +94 -0
- package/dist/post-edit.js +95 -1
- package/package.json +1 -1
package/dist/guardrails.js
CHANGED
|
@@ -1063,6 +1063,60 @@ var BUILTIN_RULES = [
|
|
|
1063
1063
|
severity: "block",
|
|
1064
1064
|
skill: "frontend-policy"
|
|
1065
1065
|
},
|
|
1066
|
+
{
|
|
1067
|
+
id: "fe-lightbox-hand-rolled",
|
|
1068
|
+
label: "Image viewers come from the primitive",
|
|
1069
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
1070
|
+
excludeFiles: [
|
|
1071
|
+
"*.test.*",
|
|
1072
|
+
"*.spec.*",
|
|
1073
|
+
"**/tests/**",
|
|
1074
|
+
"**/__tests__/**",
|
|
1075
|
+
"**/fixtures/**",
|
|
1076
|
+
"*.min.js",
|
|
1077
|
+
"**/dist/**",
|
|
1078
|
+
"**/build/**",
|
|
1079
|
+
"**/node_modules/**",
|
|
1080
|
+
"**/vendor/**",
|
|
1081
|
+
"dist/**",
|
|
1082
|
+
"build/**",
|
|
1083
|
+
"node_modules/**",
|
|
1084
|
+
"vendor/**"
|
|
1085
|
+
],
|
|
1086
|
+
scope: "file",
|
|
1087
|
+
stage: "diff",
|
|
1088
|
+
fileCheck: "fe-lightbox-hand-rolled",
|
|
1089
|
+
message: "A lightbox written by hand: a picture in a dialog, a wheel that scales it, and a zoom kept as state. The tail is long and always the same. A wheel listener is PASSIVE by default, so every notch zooms the image and scrolls the article behind the backdrop with it. Scaling around the frame's centre slides whatever was under the pointer away from it, so zooming into a face means hunting for it again. Nothing bounds the pan, so a drag strands the picture off screen with no way back but closing. The dialog is left in the page rather than portalled, and the first ancestor with `overflow: hidden` clips it. Escape does not close it, the body still scrolls under it, and focus never returns to the image it was opened from. `enigma add image` (@enigmax/primitives) is `<Image>`: a press enlarges it and the wheel zooms around the cursor by default, with a drag to pan, a pinch on touch and the keyboard driving both - and the arrows, the strip of previews, a download menu and a discard action off until asked for. The viewer is its own chunk, fetched on intent, so a page of images nobody enlarges downloads none of it. Mark the line `enigma:allow-hand-rolled-lightbox` when the viewer genuinely has to be your own (frontend-policy).",
|
|
1090
|
+
severity: "block",
|
|
1091
|
+
skill: "frontend-policy"
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
id: "fe-video-player-hand-rolled",
|
|
1095
|
+
label: "Video controls come from the primitive",
|
|
1096
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
1097
|
+
excludeFiles: [
|
|
1098
|
+
"*.test.*",
|
|
1099
|
+
"*.spec.*",
|
|
1100
|
+
"**/tests/**",
|
|
1101
|
+
"**/__tests__/**",
|
|
1102
|
+
"**/fixtures/**",
|
|
1103
|
+
"*.min.js",
|
|
1104
|
+
"**/dist/**",
|
|
1105
|
+
"**/build/**",
|
|
1106
|
+
"**/node_modules/**",
|
|
1107
|
+
"**/vendor/**",
|
|
1108
|
+
"dist/**",
|
|
1109
|
+
"build/**",
|
|
1110
|
+
"node_modules/**",
|
|
1111
|
+
"vendor/**"
|
|
1112
|
+
],
|
|
1113
|
+
scope: "file",
|
|
1114
|
+
stage: "diff",
|
|
1115
|
+
fileCheck: "fe-video-player-hand-rolled",
|
|
1116
|
+
message: "Controls written by hand over a `<video>`. What follows is the same every time: `play()` returns a promise that REJECTS - no gesture yet, an autoplay policy, no source - and the unhandled rejection leaves the button saying pause over a video that never started; the state is kept in the component instead of read back from the element's own events, so anything else that plays or pauses it (the media keys, picture in picture, the operating system) desynchronises the bar; the scrubber is a div with a click handler rather than a slider a keyboard can drive or a screen reader can read; the buffered ranges are ignored, or read as `buffered.end(0)`, which reports a range nobody is watching; the clock changes width as it passes ten minutes and shoves every control along with it; the shortcuts fire while somebody is typing in a comment box; and fullscreen calls `requestFullscreen` on a container, which iOS Safari does not have at all. `enigma add video` (@enigmax/primitives) is `<Video>`, shaped after Plyr: the same control set and the same shortcuts, the element as the source of truth, and a theme restyled through `data-*` attributes. Mark the line `enigma:allow-hand-rolled-video-player` when the player genuinely has to be your own (frontend-policy).",
|
|
1117
|
+
severity: "block",
|
|
1118
|
+
skill: "frontend-policy"
|
|
1119
|
+
},
|
|
1066
1120
|
{
|
|
1067
1121
|
id: "sec-2fa-reauth-prompt",
|
|
1068
1122
|
label: "A credential just proven is not asked for again",
|
|
@@ -1971,6 +2025,8 @@ var FILE_CHECKS = {
|
|
|
1971
2025
|
"fe-context-menu-hand-rolled": (content) => handRolledContextMenu(content),
|
|
1972
2026
|
"fe-selection-hand-rolled": (content) => handRolledSelection(content),
|
|
1973
2027
|
"fe-color-picker-hand-rolled": (content) => handRolledColorPicker(content),
|
|
2028
|
+
"fe-lightbox-hand-rolled": (content) => handRolledLightbox(content),
|
|
2029
|
+
"fe-video-player-hand-rolled": (content) => handRolledVideoPlayer(content),
|
|
1974
2030
|
"sec-2fa-reauth-prompt": (content) => twoFactorPasswordPrompt(content),
|
|
1975
2031
|
"fe-select-hand-rolled": (content) => handRolledSelect(content),
|
|
1976
2032
|
"fe-toast-hand-rolled": (content) => handRolledToast(content),
|
|
@@ -2280,6 +2336,42 @@ function handRolledColorPicker(content) {
|
|
|
2280
2336
|
}
|
|
2281
2337
|
return [];
|
|
2282
2338
|
}
|
|
2339
|
+
var LIGHTBOX_IMAGE = /<img\b|<Image\b|createElement\(\s*["'`]img["'`]/;
|
|
2340
|
+
var LIGHTBOX_WHEEL = /onWheel|@wheel|on:wheel|addEventListener\(\s*["'`]wheel["'`]/;
|
|
2341
|
+
var LIGHTBOX_ZOOM = /\bset(?:Scale|Zoom)\b|\b(?:scale|zoom)\s*[:=]\s*[0-9(]|scale\(/;
|
|
2342
|
+
var LIGHTBOX_DIALOG = /role=["']dialog["']|aria-modal|lightbox|createPortal|["']Escape["']|backdrop/i;
|
|
2343
|
+
var LIGHTBOX_MITIGATED = /@enigmax\/primitives|data-enigma-image|ImageViewer\b|yet-another-react-lightbox|react-image-lightbox|photoswipe|medium-zoom|viewerjs|fancybox|lightgallery|react-medium-image-zoom|enigma:allow-hand-rolled-lightbox/i;
|
|
2344
|
+
function handRolledLightbox(content) {
|
|
2345
|
+
if (LIGHTBOX_MITIGATED.test(content)) return [];
|
|
2346
|
+
if (!LIGHTBOX_IMAGE.test(content) || !LIGHTBOX_WHEEL.test(content)) return [];
|
|
2347
|
+
if (!LIGHTBOX_ZOOM.test(content) || !LIGHTBOX_DIALOG.test(content)) return [];
|
|
2348
|
+
const lines = content.split("\n");
|
|
2349
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2350
|
+
const line = lines[i];
|
|
2351
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2352
|
+
if (!LIGHTBOX_WHEEL.test(line)) continue;
|
|
2353
|
+
return [{ line: i + 1, detail: "a picture in a dialog, a wheel that scales it, and the zoom it keeps" }];
|
|
2354
|
+
}
|
|
2355
|
+
return [];
|
|
2356
|
+
}
|
|
2357
|
+
var VIDEO_ELEMENT = /<video\b|createElement\(\s*["'`]video["'`]|HTMLVideoElement/;
|
|
2358
|
+
var VIDEO_DRIVEN = /\.play\(\)|\.pause\(\)|\bplaybackRate\b|requestPictureInPicture/;
|
|
2359
|
+
var VIDEO_TIMELINE = /\bcurrentTime\b|timeupdate|\.buffered\b/i;
|
|
2360
|
+
var VIDEO_CONTROLS = /aria-label=["'](?:Play|Pause|Mute|Seek|Fullscreen)|onClick=\{[^}]*(?:play|pause)|requestFullscreen|toggleFullscreen|isPlaying|setPlaying/i;
|
|
2361
|
+
var VIDEO_MITIGATED = /@enigmax\/primitives|data-enigma-video|["'`]plyr|from\s+["'`]plyr|video\.js|videojs|react-player|vidstack|media-chrome|@vime|shaka-player|enigma:allow-hand-rolled-video-player/i;
|
|
2362
|
+
function handRolledVideoPlayer(content) {
|
|
2363
|
+
if (VIDEO_MITIGATED.test(content)) return [];
|
|
2364
|
+
if (!VIDEO_ELEMENT.test(content) || !VIDEO_DRIVEN.test(content)) return [];
|
|
2365
|
+
if (!VIDEO_TIMELINE.test(content) || !VIDEO_CONTROLS.test(content)) return [];
|
|
2366
|
+
const lines = content.split("\n");
|
|
2367
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2368
|
+
const line = lines[i];
|
|
2369
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
2370
|
+
if (!VIDEO_DRIVEN.test(line)) continue;
|
|
2371
|
+
return [{ line: i + 1, detail: "a video element, its playback driven by code, and a bar of controls over it" }];
|
|
2372
|
+
}
|
|
2373
|
+
return [];
|
|
2374
|
+
}
|
|
2283
2375
|
var TWOFA_SUBJECT = /2fa|two[-_ ]?factor|totp|mfa|authenticator|otpauth/i;
|
|
2284
2376
|
var TWOFA_ENROLL = /enroll?(?!ed)|set[-_ ]?up|activate(?!d)|enable(?!d)|provision(?!ed)/i;
|
|
2285
2377
|
var TWOFA_PASSWORD = /type=["']password["']|\bpassword\s*[:=]|\bpassword\b\s*[,)]|currentPassword|current_password/i;
|
|
@@ -2930,10 +3022,12 @@ export {
|
|
|
2930
3022
|
formatFindings,
|
|
2931
3023
|
handRolledColorPicker,
|
|
2932
3024
|
handRolledContextMenu,
|
|
3025
|
+
handRolledLightbox,
|
|
2933
3026
|
handRolledPalette,
|
|
2934
3027
|
handRolledSelect,
|
|
2935
3028
|
handRolledSelection,
|
|
2936
3029
|
handRolledToast,
|
|
3030
|
+
handRolledVideoPlayer,
|
|
2937
3031
|
loadRules,
|
|
2938
3032
|
missingPathAlias,
|
|
2939
3033
|
missingWindowsHide,
|
package/dist/post-edit.js
CHANGED
|
@@ -569,10 +569,12 @@ __export(guardrails_exports, {
|
|
|
569
569
|
formatFindings: () => formatFindings,
|
|
570
570
|
handRolledColorPicker: () => handRolledColorPicker,
|
|
571
571
|
handRolledContextMenu: () => handRolledContextMenu,
|
|
572
|
+
handRolledLightbox: () => handRolledLightbox,
|
|
572
573
|
handRolledPalette: () => handRolledPalette,
|
|
573
574
|
handRolledSelect: () => handRolledSelect,
|
|
574
575
|
handRolledSelection: () => handRolledSelection,
|
|
575
576
|
handRolledToast: () => handRolledToast,
|
|
577
|
+
handRolledVideoPlayer: () => handRolledVideoPlayer,
|
|
576
578
|
loadRules: () => loadRules,
|
|
577
579
|
missingPathAlias: () => missingPathAlias,
|
|
578
580
|
missingWindowsHide: () => missingWindowsHide,
|
|
@@ -845,6 +847,32 @@ function handRolledColorPicker(content) {
|
|
|
845
847
|
}
|
|
846
848
|
return [];
|
|
847
849
|
}
|
|
850
|
+
function handRolledLightbox(content) {
|
|
851
|
+
if (LIGHTBOX_MITIGATED.test(content)) return [];
|
|
852
|
+
if (!LIGHTBOX_IMAGE.test(content) || !LIGHTBOX_WHEEL.test(content)) return [];
|
|
853
|
+
if (!LIGHTBOX_ZOOM.test(content) || !LIGHTBOX_DIALOG.test(content)) return [];
|
|
854
|
+
const lines = content.split("\n");
|
|
855
|
+
for (let i = 0; i < lines.length; i++) {
|
|
856
|
+
const line = lines[i];
|
|
857
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
858
|
+
if (!LIGHTBOX_WHEEL.test(line)) continue;
|
|
859
|
+
return [{ line: i + 1, detail: "a picture in a dialog, a wheel that scales it, and the zoom it keeps" }];
|
|
860
|
+
}
|
|
861
|
+
return [];
|
|
862
|
+
}
|
|
863
|
+
function handRolledVideoPlayer(content) {
|
|
864
|
+
if (VIDEO_MITIGATED.test(content)) return [];
|
|
865
|
+
if (!VIDEO_ELEMENT.test(content) || !VIDEO_DRIVEN.test(content)) return [];
|
|
866
|
+
if (!VIDEO_TIMELINE.test(content) || !VIDEO_CONTROLS.test(content)) return [];
|
|
867
|
+
const lines = content.split("\n");
|
|
868
|
+
for (let i = 0; i < lines.length; i++) {
|
|
869
|
+
const line = lines[i];
|
|
870
|
+
if (COMMENT_LINE.test(line) || /enigma:/.test(line)) continue;
|
|
871
|
+
if (!VIDEO_DRIVEN.test(line)) continue;
|
|
872
|
+
return [{ line: i + 1, detail: "a video element, its playback driven by code, and a bar of controls over it" }];
|
|
873
|
+
}
|
|
874
|
+
return [];
|
|
875
|
+
}
|
|
848
876
|
function twoFactorPasswordPrompt(content) {
|
|
849
877
|
if (TWOFA_MITIGATED.test(content)) return [];
|
|
850
878
|
if (!TWOFA_SUBJECT.test(content) || !TWOFA_ENROLL.test(content)) return [];
|
|
@@ -1434,7 +1462,7 @@ enigma-guardrails: BLOCKED - ${r.blocks.length} convention violation(s):`);
|
|
|
1434
1462
|
console.log(`enigma-guardrails: ${r.count} ${all ? "tracked" : "staged"} file(s) checked, no blocking violations.`);
|
|
1435
1463
|
return 0;
|
|
1436
1464
|
}
|
|
1437
|
-
var COMMENT_LINE, SKELETON_GUARD_SRC, SKELETON_SIGNAL_SRC, BUILTIN_RULES, PROJECT_CHECKS, CURRENT_PASSWORD, NEW_PASSWORD, NEW_PASSWORD_PROP, NOT_A_JSX_PROP, FILE_CHECKS, FIXERS, SPAWNERS, MUTATING_REQUEST, ENTITY_WRITE, OPTIMISTIC_SIGNAL, ALLOW_SERVER_FIRST, RESULT_BINDING, BLOCK_LOOKBACK, BLOCK_LOOKAHEAD, TEXTAREA, TEXTAREA_LOWER, TEXTAREA_UPPER, TEXTAREA_FIXED, TEXTAREA_AUTOSIZE, VIEW_GUARD, LOADING_NAME, LOADING_VALUE, VIEW_GUARD_LOADING, VIEW_CHROME_HEADING, VIEW_CHROME_TEXT, VIEW_BODY_LOOKAHEAD, ALLOW_VIEW_SKELETON, SKELETON_GUARD, SKELETON_SIGNAL, REMOTE_COLLECTION_BINDINGS, LIST_BOUNDED, ALLOW_UNBOUNDED_LIST, MENU_HANDLER, MENU_POINT, MENU_STATE, MENU_MITIGATED, SELECT_RANGE_KEY, SELECT_ADD_KEY, SELECT_STATE, SELECT_MITIGATED, COLOR_MATHS, COLOR_DRAG, COLOR_MEASURE, COLOR_STATE, COLOR_MITIGATED, TWOFA_SUBJECT, TWOFA_ENROLL, TWOFA_PASSWORD, TWOFA_MITIGATED, TWOFA_WINDOW, LISTBOX_ROLE, LISTBOX_OPEN, LISTBOX_OPTIONS, LISTBOX_MITIGATED, TOAST_QUEUE, TOAST_TIMER, TOAST_MITIGATED, PALETTE_SHORTCUT, PALETTE_MODIFIER, PALETTE_OPEN, PALETTE_FILTER, PALETTE_MITIGATED, CLIP_ONE_LINE, DYNAMIC_CHILD, VALUE_REACHABLE, WRAPPER_LOOKBACK, ALLOW_CLIPPED_VALUE, CLIPPED_SIMPLE_VALUE, NOT_TEXT_BINDING, ASYNC_ROUTE, SERVER_DATA_AWAIT, STREAM_BOUNDARY, ALLOW_BLOCKING_PAGE, LOADING_BOUNDARY_FILE, SPECIFIER, MODULE_EXT, JS_EXT, tsconfigCache, NAMED_IMPORT, GENERIC_ACCOUNTS, INTERNAL_MODULE, globCache, DIFF_HUNK, LEDGER_MAX_BYTES, LEDGER_KEEP, LEDGER_KEEP_REPLY, grEntry, isGrEntry;
|
|
1465
|
+
var COMMENT_LINE, SKELETON_GUARD_SRC, SKELETON_SIGNAL_SRC, BUILTIN_RULES, PROJECT_CHECKS, CURRENT_PASSWORD, NEW_PASSWORD, NEW_PASSWORD_PROP, NOT_A_JSX_PROP, FILE_CHECKS, FIXERS, SPAWNERS, MUTATING_REQUEST, ENTITY_WRITE, OPTIMISTIC_SIGNAL, ALLOW_SERVER_FIRST, RESULT_BINDING, BLOCK_LOOKBACK, BLOCK_LOOKAHEAD, TEXTAREA, TEXTAREA_LOWER, TEXTAREA_UPPER, TEXTAREA_FIXED, TEXTAREA_AUTOSIZE, VIEW_GUARD, LOADING_NAME, LOADING_VALUE, VIEW_GUARD_LOADING, VIEW_CHROME_HEADING, VIEW_CHROME_TEXT, VIEW_BODY_LOOKAHEAD, ALLOW_VIEW_SKELETON, SKELETON_GUARD, SKELETON_SIGNAL, REMOTE_COLLECTION_BINDINGS, LIST_BOUNDED, ALLOW_UNBOUNDED_LIST, MENU_HANDLER, MENU_POINT, MENU_STATE, MENU_MITIGATED, SELECT_RANGE_KEY, SELECT_ADD_KEY, SELECT_STATE, SELECT_MITIGATED, COLOR_MATHS, COLOR_DRAG, COLOR_MEASURE, COLOR_STATE, COLOR_MITIGATED, LIGHTBOX_IMAGE, LIGHTBOX_WHEEL, LIGHTBOX_ZOOM, LIGHTBOX_DIALOG, LIGHTBOX_MITIGATED, VIDEO_ELEMENT, VIDEO_DRIVEN, VIDEO_TIMELINE, VIDEO_CONTROLS, VIDEO_MITIGATED, TWOFA_SUBJECT, TWOFA_ENROLL, TWOFA_PASSWORD, TWOFA_MITIGATED, TWOFA_WINDOW, LISTBOX_ROLE, LISTBOX_OPEN, LISTBOX_OPTIONS, LISTBOX_MITIGATED, TOAST_QUEUE, TOAST_TIMER, TOAST_MITIGATED, PALETTE_SHORTCUT, PALETTE_MODIFIER, PALETTE_OPEN, PALETTE_FILTER, PALETTE_MITIGATED, CLIP_ONE_LINE, DYNAMIC_CHILD, VALUE_REACHABLE, WRAPPER_LOOKBACK, ALLOW_CLIPPED_VALUE, CLIPPED_SIMPLE_VALUE, NOT_TEXT_BINDING, ASYNC_ROUTE, SERVER_DATA_AWAIT, STREAM_BOUNDARY, ALLOW_BLOCKING_PAGE, LOADING_BOUNDARY_FILE, SPECIFIER, MODULE_EXT, JS_EXT, tsconfigCache, NAMED_IMPORT, GENERIC_ACCOUNTS, INTERNAL_MODULE, globCache, DIFF_HUNK, LEDGER_MAX_BYTES, LEDGER_KEEP, LEDGER_KEEP_REPLY, grEntry, isGrEntry;
|
|
1438
1466
|
var init_guardrails = __esm({
|
|
1439
1467
|
"src/guardrails.ts"() {
|
|
1440
1468
|
"use strict";
|
|
@@ -2495,6 +2523,60 @@ var init_guardrails = __esm({
|
|
|
2495
2523
|
severity: "block",
|
|
2496
2524
|
skill: "frontend-policy"
|
|
2497
2525
|
},
|
|
2526
|
+
{
|
|
2527
|
+
id: "fe-lightbox-hand-rolled",
|
|
2528
|
+
label: "Image viewers come from the primitive",
|
|
2529
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
2530
|
+
excludeFiles: [
|
|
2531
|
+
"*.test.*",
|
|
2532
|
+
"*.spec.*",
|
|
2533
|
+
"**/tests/**",
|
|
2534
|
+
"**/__tests__/**",
|
|
2535
|
+
"**/fixtures/**",
|
|
2536
|
+
"*.min.js",
|
|
2537
|
+
"**/dist/**",
|
|
2538
|
+
"**/build/**",
|
|
2539
|
+
"**/node_modules/**",
|
|
2540
|
+
"**/vendor/**",
|
|
2541
|
+
"dist/**",
|
|
2542
|
+
"build/**",
|
|
2543
|
+
"node_modules/**",
|
|
2544
|
+
"vendor/**"
|
|
2545
|
+
],
|
|
2546
|
+
scope: "file",
|
|
2547
|
+
stage: "diff",
|
|
2548
|
+
fileCheck: "fe-lightbox-hand-rolled",
|
|
2549
|
+
message: "A lightbox written by hand: a picture in a dialog, a wheel that scales it, and a zoom kept as state. The tail is long and always the same. A wheel listener is PASSIVE by default, so every notch zooms the image and scrolls the article behind the backdrop with it. Scaling around the frame's centre slides whatever was under the pointer away from it, so zooming into a face means hunting for it again. Nothing bounds the pan, so a drag strands the picture off screen with no way back but closing. The dialog is left in the page rather than portalled, and the first ancestor with `overflow: hidden` clips it. Escape does not close it, the body still scrolls under it, and focus never returns to the image it was opened from. `enigma add image` (@enigmax/primitives) is `<Image>`: a press enlarges it and the wheel zooms around the cursor by default, with a drag to pan, a pinch on touch and the keyboard driving both - and the arrows, the strip of previews, a download menu and a discard action off until asked for. The viewer is its own chunk, fetched on intent, so a page of images nobody enlarges downloads none of it. Mark the line `enigma:allow-hand-rolled-lightbox` when the viewer genuinely has to be your own (frontend-policy).",
|
|
2550
|
+
severity: "block",
|
|
2551
|
+
skill: "frontend-policy"
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
id: "fe-video-player-hand-rolled",
|
|
2555
|
+
label: "Video controls come from the primitive",
|
|
2556
|
+
files: ["*.tsx", "*.jsx", "*.vue", "*.svelte", "*.astro", "*.ts", "*.js"],
|
|
2557
|
+
excludeFiles: [
|
|
2558
|
+
"*.test.*",
|
|
2559
|
+
"*.spec.*",
|
|
2560
|
+
"**/tests/**",
|
|
2561
|
+
"**/__tests__/**",
|
|
2562
|
+
"**/fixtures/**",
|
|
2563
|
+
"*.min.js",
|
|
2564
|
+
"**/dist/**",
|
|
2565
|
+
"**/build/**",
|
|
2566
|
+
"**/node_modules/**",
|
|
2567
|
+
"**/vendor/**",
|
|
2568
|
+
"dist/**",
|
|
2569
|
+
"build/**",
|
|
2570
|
+
"node_modules/**",
|
|
2571
|
+
"vendor/**"
|
|
2572
|
+
],
|
|
2573
|
+
scope: "file",
|
|
2574
|
+
stage: "diff",
|
|
2575
|
+
fileCheck: "fe-video-player-hand-rolled",
|
|
2576
|
+
message: "Controls written by hand over a `<video>`. What follows is the same every time: `play()` returns a promise that REJECTS - no gesture yet, an autoplay policy, no source - and the unhandled rejection leaves the button saying pause over a video that never started; the state is kept in the component instead of read back from the element's own events, so anything else that plays or pauses it (the media keys, picture in picture, the operating system) desynchronises the bar; the scrubber is a div with a click handler rather than a slider a keyboard can drive or a screen reader can read; the buffered ranges are ignored, or read as `buffered.end(0)`, which reports a range nobody is watching; the clock changes width as it passes ten minutes and shoves every control along with it; the shortcuts fire while somebody is typing in a comment box; and fullscreen calls `requestFullscreen` on a container, which iOS Safari does not have at all. `enigma add video` (@enigmax/primitives) is `<Video>`, shaped after Plyr: the same control set and the same shortcuts, the element as the source of truth, and a theme restyled through `data-*` attributes. Mark the line `enigma:allow-hand-rolled-video-player` when the player genuinely has to be your own (frontend-policy).",
|
|
2577
|
+
severity: "block",
|
|
2578
|
+
skill: "frontend-policy"
|
|
2579
|
+
},
|
|
2498
2580
|
{
|
|
2499
2581
|
id: "sec-2fa-reauth-prompt",
|
|
2500
2582
|
label: "A credential just proven is not asked for again",
|
|
@@ -3391,6 +3473,8 @@ var init_guardrails = __esm({
|
|
|
3391
3473
|
"fe-context-menu-hand-rolled": (content) => handRolledContextMenu(content),
|
|
3392
3474
|
"fe-selection-hand-rolled": (content) => handRolledSelection(content),
|
|
3393
3475
|
"fe-color-picker-hand-rolled": (content) => handRolledColorPicker(content),
|
|
3476
|
+
"fe-lightbox-hand-rolled": (content) => handRolledLightbox(content),
|
|
3477
|
+
"fe-video-player-hand-rolled": (content) => handRolledVideoPlayer(content),
|
|
3394
3478
|
"sec-2fa-reauth-prompt": (content) => twoFactorPasswordPrompt(content),
|
|
3395
3479
|
"fe-select-hand-rolled": (content) => handRolledSelect(content),
|
|
3396
3480
|
"fe-toast-hand-rolled": (content) => handRolledToast(content),
|
|
@@ -3467,6 +3551,16 @@ var init_guardrails = __esm({
|
|
|
3467
3551
|
COLOR_MEASURE = /getBoundingClientRect/;
|
|
3468
3552
|
COLOR_STATE = /\bset(?:Color|Colour|Hue|Saturation|Lightness|Brightness|Alpha|Opacity)\b|\b(?:hue|saturation)\s*[:=]/i;
|
|
3469
3553
|
COLOR_MITIGATED = /@enigmax\/primitives|type=["']color["']|react-colorful|react-color|@uiw\/react-color|vue-color|ngx-color|@radix-ui|enigma:allow-hand-rolled-color-picker/;
|
|
3554
|
+
LIGHTBOX_IMAGE = /<img\b|<Image\b|createElement\(\s*["'`]img["'`]/;
|
|
3555
|
+
LIGHTBOX_WHEEL = /onWheel|@wheel|on:wheel|addEventListener\(\s*["'`]wheel["'`]/;
|
|
3556
|
+
LIGHTBOX_ZOOM = /\bset(?:Scale|Zoom)\b|\b(?:scale|zoom)\s*[:=]\s*[0-9(]|scale\(/;
|
|
3557
|
+
LIGHTBOX_DIALOG = /role=["']dialog["']|aria-modal|lightbox|createPortal|["']Escape["']|backdrop/i;
|
|
3558
|
+
LIGHTBOX_MITIGATED = /@enigmax\/primitives|data-enigma-image|ImageViewer\b|yet-another-react-lightbox|react-image-lightbox|photoswipe|medium-zoom|viewerjs|fancybox|lightgallery|react-medium-image-zoom|enigma:allow-hand-rolled-lightbox/i;
|
|
3559
|
+
VIDEO_ELEMENT = /<video\b|createElement\(\s*["'`]video["'`]|HTMLVideoElement/;
|
|
3560
|
+
VIDEO_DRIVEN = /\.play\(\)|\.pause\(\)|\bplaybackRate\b|requestPictureInPicture/;
|
|
3561
|
+
VIDEO_TIMELINE = /\bcurrentTime\b|timeupdate|\.buffered\b/i;
|
|
3562
|
+
VIDEO_CONTROLS = /aria-label=["'](?:Play|Pause|Mute|Seek|Fullscreen)|onClick=\{[^}]*(?:play|pause)|requestFullscreen|toggleFullscreen|isPlaying|setPlaying/i;
|
|
3563
|
+
VIDEO_MITIGATED = /@enigmax\/primitives|data-enigma-video|["'`]plyr|from\s+["'`]plyr|video\.js|videojs|react-player|vidstack|media-chrome|@vime|shaka-player|enigma:allow-hand-rolled-video-player/i;
|
|
3470
3564
|
TWOFA_SUBJECT = /2fa|two[-_ ]?factor|totp|mfa|authenticator|otpauth/i;
|
|
3471
3565
|
TWOFA_ENROLL = /enroll?(?!ed)|set[-_ ]?up|activate(?!d)|enable(?!d)|provision(?!ed)/i;
|
|
3472
3566
|
TWOFA_PASSWORD = /type=["']password["']|\bpassword\s*[:=]|\bpassword\b\s*[,)]|currentPassword|current_password/i;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enigma-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"description": "Everything you need to work with a coding agent: install shared policy skills for Claude Code, OpenAI Codex and opencode, and set up portable git security hooks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|