snice 7.0.3 → 7.1.1
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/.agents/skills/snice/SKILL.md +4 -0
- package/.agents/skills/snice/references/core-kitchen-sink.ts +39 -1
- package/README.md +64 -14
- package/adapters/react/SniceProvider.js +2 -2
- package/adapters/react/SniceRouter.js +2 -2
- package/adapters/react/matchRoute.js +2 -2
- package/adapters/react/useRequestHandler.js +2 -2
- package/bin/analyzer-contracts.js +6 -1
- package/bin/postinstall.js +59 -0
- package/bin/project-analyzer.js +466 -5
- package/bin/snice.js +3 -0
- package/dist/app-context.d.ts +11 -0
- package/dist/cdn/accordion/snice-accordion.js +2 -2
- package/dist/cdn/action-bar/snice-action-bar.js +2 -2
- package/dist/cdn/activity-feed/snice-activity-feed.js +2 -2
- package/dist/cdn/alert/snice-alert.js +2 -2
- package/dist/cdn/app-tiles/snice-app-tiles.js +2 -2
- package/dist/cdn/approval-flow/snice-approval-flow.js +2 -2
- package/dist/cdn/audio-recorder/snice-audio-recorder.js +2 -2
- package/dist/cdn/availability/snice-availability.js +2 -2
- package/dist/cdn/avatar/snice-avatar.js +2 -2
- package/dist/cdn/avatar-group/snice-avatar-group.js +2 -2
- package/dist/cdn/badge/snice-badge.js +2 -2
- package/dist/cdn/banner/snice-banner.js +2 -2
- package/dist/cdn/binpack/snice-binpack.js +2 -2
- package/dist/cdn/book/snice-book.js +2 -2
- package/dist/cdn/booking/snice-booking.js +2 -2
- package/dist/cdn/breadcrumbs/snice-breadcrumbs.js +2 -2
- package/dist/cdn/button/snice-button.js +2 -2
- package/dist/cdn/calendar/snice-calendar.js +2 -2
- package/dist/cdn/camera/snice-camera.js +2 -2
- package/dist/cdn/camera-annotate/snice-camera-annotate.js +2 -2
- package/dist/cdn/candlestick/snice-candlestick.js +2 -2
- package/dist/cdn/card/snice-card.js +2 -2
- package/dist/cdn/carousel/snice-carousel.js +2 -2
- package/dist/cdn/cart/snice-cart.js +2 -2
- package/dist/cdn/chart/snice-chart.js +2 -2
- package/dist/cdn/chat/snice-chat.js +2 -2
- package/dist/cdn/checkbox/snice-checkbox.js +2 -2
- package/dist/cdn/chip/snice-chip.js +2 -2
- package/dist/cdn/code-block/snice-code-block.js +2 -2
- package/dist/cdn/color-display/snice-color-display.js +2 -2
- package/dist/cdn/color-picker/snice-color-picker.js +2 -2
- package/dist/cdn/command-palette/snice-command-palette.js +2 -2
- package/dist/cdn/comments/snice-comments.js +2 -2
- package/dist/cdn/countdown/snice-countdown.js +2 -2
- package/dist/cdn/cropper/snice-cropper.js +2 -2
- package/dist/cdn/data-card/snice-data-card.js +2 -2
- package/dist/cdn/date-picker/snice-date-picker.js +2 -2
- package/dist/cdn/date-range-picker/snice-date-range-picker.js +2 -2
- package/dist/cdn/date-time-picker/snice-date-time-picker.js +2 -2
- package/dist/cdn/diff/snice-diff.js +2 -2
- package/dist/cdn/divider/snice-divider.js +2 -2
- package/dist/cdn/doc/snice-doc.js +2 -2
- package/dist/cdn/draw/snice-draw.js +2 -2
- package/dist/cdn/drawer/snice-drawer.js +2 -2
- package/dist/cdn/empty-state/snice-empty-state.js +2 -2
- package/dist/cdn/estimate/snice-estimate.js +2 -2
- package/dist/cdn/file-gallery/snice-file-gallery.js +2 -2
- package/dist/cdn/file-upload/snice-file-upload.js +2 -2
- package/dist/cdn/flip-card/snice-flip-card.js +2 -2
- package/dist/cdn/flow/snice-flow.js +2 -2
- package/dist/cdn/form-layout/snice-form-layout.js +2 -2
- package/dist/cdn/funnel/snice-funnel.js +2 -2
- package/dist/cdn/gantt/snice-gantt.js +2 -2
- package/dist/cdn/gauge/snice-gauge.js +2 -2
- package/dist/cdn/grid/snice-grid.js +2 -2
- package/dist/cdn/heatmap/snice-heatmap.js +2 -2
- package/dist/cdn/image/snice-image.js +2 -2
- package/dist/cdn/input/snice-input.js +2 -2
- package/dist/cdn/invoice/snice-invoice.js +2 -2
- package/dist/cdn/kanban/snice-kanban.js +2 -2
- package/dist/cdn/key-value/snice-key-value.js +2 -2
- package/dist/cdn/kpi/snice-kpi.js +2 -2
- package/dist/cdn/layout/README.md +17 -2
- package/dist/cdn/layout/snice-layout.js +1656 -11
- package/dist/cdn/layout/snice-layout.js.map +1 -1
- package/dist/cdn/layout/snice-layout.min.js +379 -2
- package/dist/cdn/layout/snice-layout.min.js.map +1 -1
- package/dist/cdn/leaderboard/snice-leaderboard.js +2 -2
- package/dist/cdn/link/snice-link.js +2 -2
- package/dist/cdn/link-preview/snice-link-preview.js +2 -2
- package/dist/cdn/list/snice-list.js +2 -2
- package/dist/cdn/location/snice-location.js +2 -2
- package/dist/cdn/login/snice-login.js +2 -2
- package/dist/cdn/map/snice-map.js +2 -2
- package/dist/cdn/markdown/snice-markdown.js +2 -2
- package/dist/cdn/masonry/snice-masonry.js +2 -2
- package/dist/cdn/menu/snice-menu.js +2 -2
- package/dist/cdn/message-strip/snice-message-strip.js +2 -2
- package/dist/cdn/modal/snice-modal.js +2 -2
- package/dist/cdn/music-player/snice-music-player.js +2 -2
- package/dist/cdn/nav/snice-nav.js +2 -2
- package/dist/cdn/network-graph/snice-network-graph.js +2 -2
- package/dist/cdn/notification-center/snice-notification-center.js +2 -2
- package/dist/cdn/order-tracker/snice-order-tracker.js +2 -2
- package/dist/cdn/org-chart/snice-org-chart.js +2 -2
- package/dist/cdn/pagination/snice-pagination.js +2 -2
- package/dist/cdn/paint/snice-paint.js +2 -2
- package/dist/cdn/pdf-viewer/snice-pdf-viewer.js +2 -2
- package/dist/cdn/permission-matrix/snice-permission-matrix.js +2 -2
- package/dist/cdn/podcast-player/snice-podcast-player.js +2 -2
- package/dist/cdn/popover/snice-popover.js +2 -2
- package/dist/cdn/pricing-table/snice-pricing-table.js +2 -2
- package/dist/cdn/product-card/snice-product-card.js +2 -2
- package/dist/cdn/progress/snice-progress.js +2 -2
- package/dist/cdn/progress-ring/snice-progress-ring.js +2 -2
- package/dist/cdn/qr-code/snice-qr-code.js +2 -2
- package/dist/cdn/qr-reader/snice-qr-reader.js +2 -2
- package/dist/cdn/radio/snice-radio.js +2 -2
- package/dist/cdn/range-slider/snice-range-slider.js +2 -2
- package/dist/cdn/rating/snice-rating.js +2 -2
- package/dist/cdn/receipt/snice-receipt.js +2 -2
- package/dist/cdn/recipe/snice-recipe.js +2 -2
- package/dist/cdn/runtime/README.md +2 -2
- package/dist/cdn/runtime/snice-runtime.esm.js +3825 -3337
- package/dist/cdn/runtime/snice-runtime.esm.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.esm.min.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.js +3827 -3336
- package/dist/cdn/runtime/snice-runtime.js.map +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js +1 -1
- package/dist/cdn/runtime/snice-runtime.min.js.map +1 -1
- package/dist/cdn/sankey/snice-sankey.js +2 -2
- package/dist/cdn/segmented-control/snice-segmented-control.js +2 -2
- package/dist/cdn/select/snice-select.js +2 -2
- package/dist/cdn/skeleton/snice-skeleton.js +2 -2
- package/dist/cdn/slider/snice-slider.js +2 -2
- package/dist/cdn/sortable/snice-sortable.js +2 -2
- package/dist/cdn/sparkline/snice-sparkline.js +2 -2
- package/dist/cdn/spinner/snice-spinner.js +2 -2
- package/dist/cdn/split-button/snice-split-button.js +2 -2
- package/dist/cdn/split-pane/snice-split-pane.js +2 -2
- package/dist/cdn/spotlight/snice-spotlight.js +2 -2
- package/dist/cdn/stat-group/snice-stat-group.js +2 -2
- package/dist/cdn/step-input/snice-step-input.js +2 -2
- package/dist/cdn/stepper/snice-stepper.js +2 -2
- package/dist/cdn/switch/snice-switch.js +2 -2
- package/dist/cdn/table/README.md +6 -2
- package/dist/cdn/table/snice-table.esm.js +347 -54
- package/dist/cdn/table/snice-table.esm.js.map +1 -1
- package/dist/cdn/table/snice-table.esm.min.js +105 -44
- package/dist/cdn/table/snice-table.esm.min.js.map +1 -1
- package/dist/cdn/table/snice-table.js +348 -57
- package/dist/cdn/table/snice-table.js.map +1 -1
- package/dist/cdn/table/snice-table.min.js +153 -92
- package/dist/cdn/table/snice-table.min.js.map +1 -1
- package/dist/cdn/tabs/snice-tabs.js +2 -2
- package/dist/cdn/tag/snice-tag.js +2 -2
- package/dist/cdn/tag-input/snice-tag-input.js +2 -2
- package/dist/cdn/terminal/snice-terminal.js +2 -2
- package/dist/cdn/testimonial/snice-testimonial.js +2 -2
- package/dist/cdn/textarea/snice-textarea.js +2 -2
- package/dist/cdn/time-picker/snice-time-picker.js +2 -2
- package/dist/cdn/time-range-picker/snice-time-range-picker.js +2 -2
- package/dist/cdn/timeline/snice-timeline.js +2 -2
- package/dist/cdn/timer/snice-timer.js +2 -2
- package/dist/cdn/toast/snice-toast.js +2 -2
- package/dist/cdn/tooltip/snice-tooltip.js +2 -2
- package/dist/cdn/tree/snice-tree.js +2 -2
- package/dist/cdn/treemap/snice-treemap.js +2 -2
- package/dist/cdn/user-card/snice-user-card.js +2 -2
- package/dist/cdn/video-player/snice-video-player.js +2 -2
- package/dist/cdn/virtual-scroller/snice-virtual-scroller.js +2 -2
- package/dist/cdn/waterfall/snice-waterfall.js +2 -2
- package/dist/cdn/weather/snice-weather.js +2 -2
- package/dist/cdn/work-order/snice-work-order.js +2 -2
- package/dist/context-provider.d.ts +16 -0
- package/dist/controller.d.ts +5 -0
- package/dist/daemon-target.d.ts +10 -0
- package/dist/daemon.d.ts +10 -0
- package/dist/index.cjs +3821 -3330
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.esm.js +3819 -3331
- package/dist/index.esm.js.map +1 -1
- package/dist/index.iife.js +3821 -3330
- package/dist/index.iife.js.map +1 -1
- package/dist/keyboard-filter.d.ts +25 -0
- package/dist/on.d.ts +1 -1
- package/dist/parts.d.ts +2 -19
- package/dist/react/SniceProvider.js +2 -2
- package/dist/react/SniceRouter.js +2 -2
- package/dist/react/index.js +2 -2
- package/dist/react/matchRoute.js +2 -2
- package/dist/react/useRequestHandler.js +2 -2
- package/dist/request-response.d.ts +9 -1
- package/dist/symbols.cjs +19 -2
- package/dist/symbols.cjs.map +1 -1
- package/dist/symbols.d.ts +5 -0
- package/dist/symbols.esm.js +15 -3
- package/dist/symbols.esm.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/transitions.cjs +2 -2
- package/dist/transitions.esm.js +2 -2
- package/dist/types/app-context.d.ts +9 -58
- package/dist/types/daemon.d.ts +8 -0
- package/dist/types/dispatch-options.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/on-options.d.ts +5 -0
- package/dist/types/request-options.d.ts +2 -0
- package/dist/types/respond-options.d.ts +2 -0
- package/docs/ai/DEVELOPMENT.md +15 -1
- package/docs/ai/README.md +5 -1
- package/docs/ai/api.md +13 -4
- package/docs/ai/architecture.md +51 -2
- package/docs/ai/bindings.md +1 -1
- package/docs/ai/cli.md +9 -0
- package/docs/ai/controllers.md +1 -1
- package/docs/ai/daemons.md +80 -0
- package/docs/ai/decorators.md +7 -5
- package/docs/ai/events.md +6 -0
- package/docs/ai/patterns.md +49 -4
- package/docs/ai/request-response.md +26 -9
- package/docs/ai/routing.md +16 -1
- package/docs/bindings.md +1 -1
- package/docs/cli.md +7 -0
- package/docs/controllers.md +8 -2
- package/docs/daemons.md +200 -0
- package/docs/events.md +19 -2
- package/docs/request-response.md +20 -13
- package/docs/routing.md +25 -1
- package/package.json +6 -4
package/bin/project-analyzer.js
CHANGED
|
@@ -450,6 +450,12 @@ const RULE_DEFINITIONS = [
|
|
|
450
450
|
fix: "Add type: 'hash' or type: 'pushstate' to the Router options."
|
|
451
451
|
});
|
|
452
452
|
}
|
|
453
|
+
if (!/\b(?:target|outlet)\b\s*(?=[:,}])/.test(call.body)) {
|
|
454
|
+
context.report(call.index, {
|
|
455
|
+
message: 'Router() requires a target selector for the page outlet.',
|
|
456
|
+
fix: "Add target: '#app' and ensure that element exists before initialize() runs."
|
|
457
|
+
});
|
|
458
|
+
}
|
|
453
459
|
const outletIndex = call.body.search(/\boutlet\s*:/);
|
|
454
460
|
if (outletIndex >= 0 && !/\btarget\s*:/.test(call.body)) {
|
|
455
461
|
context.report(call.bodyStart + outletIndex, {
|
|
@@ -1101,11 +1107,43 @@ const RULE_DEFINITIONS = [
|
|
|
1101
1107
|
projectOnly: true,
|
|
1102
1108
|
check() {}
|
|
1103
1109
|
},
|
|
1110
|
+
{
|
|
1111
|
+
id: 'snice/router-initialization',
|
|
1112
|
+
severity: 'error',
|
|
1113
|
+
category: 'router',
|
|
1114
|
+
description: 'Require each constructed Router instance to be initialized.',
|
|
1115
|
+
projectOnly: true,
|
|
1116
|
+
check() {}
|
|
1117
|
+
},
|
|
1118
|
+
{
|
|
1119
|
+
id: 'snice/page-element-double-decoration',
|
|
1120
|
+
severity: 'error',
|
|
1121
|
+
category: 'router',
|
|
1122
|
+
description: 'Reject combining Router page and element decorators on the same class.',
|
|
1123
|
+
projectOnly: true,
|
|
1124
|
+
check() {}
|
|
1125
|
+
},
|
|
1126
|
+
{
|
|
1127
|
+
id: 'snice/recommend-project-structure',
|
|
1128
|
+
severity: 'suggestion',
|
|
1129
|
+
category: 'architecture',
|
|
1130
|
+
description: 'Recommend conventional folders for pages, components, controllers, and daemons.',
|
|
1131
|
+
projectOnly: true,
|
|
1132
|
+
check() {}
|
|
1133
|
+
},
|
|
1134
|
+
{
|
|
1135
|
+
id: 'snice/recommend-page-controller',
|
|
1136
|
+
severity: 'suggestion',
|
|
1137
|
+
category: 'architecture',
|
|
1138
|
+
description: 'Recommend extracting substantial or repeated non-visual page logic into a controller.',
|
|
1139
|
+
projectOnly: true,
|
|
1140
|
+
check() {}
|
|
1141
|
+
},
|
|
1104
1142
|
{
|
|
1105
1143
|
id: 'snice/unused-dependency',
|
|
1106
1144
|
severity: 'error',
|
|
1107
1145
|
category: 'configuration',
|
|
1108
|
-
description: 'Reject a project that declares the Snice dependency but never
|
|
1146
|
+
description: 'Reject a project that declares the Snice dependency but never uses, registers, or renders it.',
|
|
1109
1147
|
projectOnly: true,
|
|
1110
1148
|
check() {}
|
|
1111
1149
|
},
|
|
@@ -1213,6 +1251,9 @@ export function analyzeProject(files) {
|
|
|
1213
1251
|
const snicePackageManifests = [];
|
|
1214
1252
|
const requestChannels = [];
|
|
1215
1253
|
const respondChannels = new Set();
|
|
1254
|
+
const routerConstructions = [];
|
|
1255
|
+
const architectureDeclarations = [];
|
|
1256
|
+
const pageClasses = [];
|
|
1216
1257
|
let usesSnice = false;
|
|
1217
1258
|
|
|
1218
1259
|
for (const file of normalized) {
|
|
@@ -1223,6 +1264,22 @@ export function analyzeProject(files) {
|
|
|
1223
1264
|
}
|
|
1224
1265
|
const analyzable = maskComments(file.source);
|
|
1225
1266
|
const provenance = buildSourceProvenance(analyzable);
|
|
1267
|
+
routerConstructions.push(
|
|
1268
|
+
...findRouterConstructions(analyzable, localsFor(provenance.rootBindings, 'Router'))
|
|
1269
|
+
.map(construction => ({ ...construction, file }))
|
|
1270
|
+
);
|
|
1271
|
+
for (const convention of [
|
|
1272
|
+
{ decorator: 'page', folder: 'pages', kind: 'page' },
|
|
1273
|
+
{ decorator: 'element', folder: 'components', kind: 'component' },
|
|
1274
|
+
{ decorator: 'controller', folder: 'controllers', kind: 'controller' },
|
|
1275
|
+
{ decorator: 'daemon', folder: 'daemons', kind: 'daemon' }
|
|
1276
|
+
]) {
|
|
1277
|
+
for (const declaration of findDecoratedClasses(analyzable, convention.decorator)) {
|
|
1278
|
+
const architectural = { ...declaration, ...convention, file };
|
|
1279
|
+
architectureDeclarations.push(architectural);
|
|
1280
|
+
if (convention.decorator === 'page') pageClasses.push(architectural);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1226
1283
|
for (const match of analyzable.matchAll(/@request\(\s*['"]([^'"]+)['"]\s*(?:,\s*\{([^}]*)\})?/g)) {
|
|
1227
1284
|
requestChannels.push({
|
|
1228
1285
|
file,
|
|
@@ -1240,7 +1297,12 @@ export function analyzeProject(files) {
|
|
|
1240
1297
|
if (COMPONENT_MODULE_PATHS.has(entry.path) && !isTypeOnlyImport(entry)) {
|
|
1241
1298
|
registrations.add(entry.path);
|
|
1242
1299
|
}
|
|
1243
|
-
if (
|
|
1300
|
+
if (
|
|
1301
|
+
(entry.path === 'snice' || entry.path.startsWith('snice/')) &&
|
|
1302
|
+
importHasProjectUse(analyzable, entry)
|
|
1303
|
+
) {
|
|
1304
|
+
usesSnice = true;
|
|
1305
|
+
}
|
|
1244
1306
|
}
|
|
1245
1307
|
if (!usesSnice && findOpeningTags(analyzable, Object.keys(COMPONENT_CONTRACTS)).length) {
|
|
1246
1308
|
usesSnice = true;
|
|
@@ -1407,14 +1469,123 @@ export function analyzeProject(files) {
|
|
|
1407
1469
|
});
|
|
1408
1470
|
}
|
|
1409
1471
|
|
|
1472
|
+
const projectCode = normalized
|
|
1473
|
+
.filter(file => !/(?:^|[/\\])package\.json$/.test(file.filename))
|
|
1474
|
+
.map(file => maskComments(file.source))
|
|
1475
|
+
.join('\n');
|
|
1476
|
+
for (const construction of routerConstructions) {
|
|
1477
|
+
const initialized = construction.initializers.some(name => hasInvocation(projectCode, name)) ||
|
|
1478
|
+
(construction.instance && new RegExp(
|
|
1479
|
+
`\\b${escapeRegExp(construction.instance)}\\s*\\.\\s*initialize\\s*\\(`
|
|
1480
|
+
).test(projectCode));
|
|
1481
|
+
if (initialized) continue;
|
|
1482
|
+
const location = sourceLocation(construction.file.source, construction.index);
|
|
1483
|
+
diagnostics.push({
|
|
1484
|
+
severity: 'error',
|
|
1485
|
+
ruleId: 'snice/router-initialization',
|
|
1486
|
+
message: 'Router() is constructed but its initialize() function is never called, so registered pages will not start routing.',
|
|
1487
|
+
fix: 'Export the Router-returned initialize function, import all page modules, then call initialize() from the application entry point. Review docs/ai/routing.md.',
|
|
1488
|
+
file: construction.file.filename,
|
|
1489
|
+
line: location.line,
|
|
1490
|
+
column: location.column
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
const doubleDecoratedPages = new Set();
|
|
1495
|
+
const declarationsByClass = new Map();
|
|
1496
|
+
for (const declaration of architectureDeclarations) {
|
|
1497
|
+
const key = `${declaration.file.filename}:${declaration.name}`;
|
|
1498
|
+
if (!declarationsByClass.has(key)) declarationsByClass.set(key, []);
|
|
1499
|
+
declarationsByClass.get(key).push(declaration);
|
|
1500
|
+
}
|
|
1501
|
+
for (const [key, declarations] of declarationsByClass) {
|
|
1502
|
+
const page = declarations.find(declaration => declaration.decorator === 'page');
|
|
1503
|
+
const element = declarations.find(declaration => declaration.decorator === 'element');
|
|
1504
|
+
if (!page || !element) continue;
|
|
1505
|
+
doubleDecoratedPages.add(key);
|
|
1506
|
+
const location = sourceLocation(element.file.source, element.index);
|
|
1507
|
+
diagnostics.push({
|
|
1508
|
+
severity: 'error',
|
|
1509
|
+
ruleId: 'snice/page-element-double-decoration',
|
|
1510
|
+
message: `${page.name} combines @page and @element, so Snice element functionality is applied twice.`,
|
|
1511
|
+
fix: 'Remove @element from the routed class; the Router-returned @page decorator registers the custom element and applies element functionality. Review docs/ai/routing.md.',
|
|
1512
|
+
file: element.file.filename,
|
|
1513
|
+
line: location.line,
|
|
1514
|
+
column: location.column
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
for (const declaration of architectureDeclarations) {
|
|
1519
|
+
const key = `${declaration.file.filename}:${declaration.name}`;
|
|
1520
|
+
if (declaration.decorator === 'element' && doubleDecoratedPages.has(key)) continue;
|
|
1521
|
+
if (isInSourceFolder(declaration.file.filename, declaration.folder)) continue;
|
|
1522
|
+
const target = `src/${declaration.folder}/${camelToKebab(declaration.name)}.ts`;
|
|
1523
|
+
const location = sourceLocation(declaration.file.source, declaration.index);
|
|
1524
|
+
diagnostics.push({
|
|
1525
|
+
severity: 'suggestion',
|
|
1526
|
+
ruleId: 'snice/recommend-project-structure',
|
|
1527
|
+
message: `${declaration.name} is a Snice ${declaration.kind} defined outside the conventional src/${declaration.folder}/ folder.`,
|
|
1528
|
+
fix: `Move the class to ${target} and import it from the application composition root. Review docs/ai/architecture.md.`,
|
|
1529
|
+
file: declaration.file.filename,
|
|
1530
|
+
line: location.line,
|
|
1531
|
+
column: location.column
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
const pageControllerReported = new Set();
|
|
1536
|
+
for (const page of pageClasses) {
|
|
1537
|
+
const profile = pageLogicProfile(page.body);
|
|
1538
|
+
if (!profile.recommend) continue;
|
|
1539
|
+
const location = sourceLocation(page.file.source, page.index);
|
|
1540
|
+
diagnostics.push({
|
|
1541
|
+
severity: 'suggestion',
|
|
1542
|
+
ruleId: 'snice/recommend-page-controller',
|
|
1543
|
+
message: `${page.name} contains substantial non-visual logic (${profile.effectfulMethods} effectful methods, ${profile.effects} external effects, ${profile.decisions} decision points).`,
|
|
1544
|
+
fix: `Keep route orchestration and presentation in the page; move reusable data, business, timer, storage, or server behavior into a controller under src/controllers/. Review docs/ai/controllers.md and docs/ai/architecture.md.`,
|
|
1545
|
+
file: page.file.filename,
|
|
1546
|
+
line: location.line,
|
|
1547
|
+
column: location.column
|
|
1548
|
+
});
|
|
1549
|
+
pageControllerReported.add(`${page.file.filename}:${page.index}`);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
const repeatedPageMethods = new Map();
|
|
1553
|
+
for (const page of pageClasses) {
|
|
1554
|
+
for (const method of findClassMethods(page.body, page.bodyStart)) {
|
|
1555
|
+
const fingerprint = sharedLogicFingerprint(method.body);
|
|
1556
|
+
if (!fingerprint) continue;
|
|
1557
|
+
if (!repeatedPageMethods.has(fingerprint)) repeatedPageMethods.set(fingerprint, []);
|
|
1558
|
+
repeatedPageMethods.get(fingerprint).push({ page, method });
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
for (const occurrences of repeatedPageMethods.values()) {
|
|
1562
|
+
const files = [...new Set(occurrences.map(({ page }) => page.file.filename))];
|
|
1563
|
+
if (files.length < 2) continue;
|
|
1564
|
+
for (const { page, method } of occurrences) {
|
|
1565
|
+
const reportKey = `${page.file.filename}:${page.index}`;
|
|
1566
|
+
if (pageControllerReported.has(reportKey)) continue;
|
|
1567
|
+
const location = sourceLocation(page.file.source, method.index);
|
|
1568
|
+
diagnostics.push({
|
|
1569
|
+
severity: 'suggestion',
|
|
1570
|
+
ruleId: 'snice/recommend-page-controller',
|
|
1571
|
+
message: `${method.name}() repeats substantial non-visual logic across ${files.length} page files.`,
|
|
1572
|
+
fix: `Extract the shared behavior into a controller under src/controllers/ and let each page orchestrate it. Also review docs/ai/controllers.md and docs/ai/architecture.md.`,
|
|
1573
|
+
file: page.file.filename,
|
|
1574
|
+
line: location.line,
|
|
1575
|
+
column: location.column
|
|
1576
|
+
});
|
|
1577
|
+
pageControllerReported.add(reportKey);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1410
1581
|
if (!usesSnice) {
|
|
1411
1582
|
for (const manifest of snicePackageManifests) {
|
|
1412
1583
|
const location = sourceLocation(manifest.source, manifest.index);
|
|
1413
1584
|
diagnostics.push({
|
|
1414
1585
|
severity: 'error',
|
|
1415
1586
|
ruleId: 'snice/unused-dependency',
|
|
1416
|
-
message: 'package.json declares the snice dependency, but no project source
|
|
1417
|
-
fix: "Use
|
|
1587
|
+
message: 'package.json declares the snice dependency, but no project source references a Snice import, registers a Snice component, or renders a released snice-* custom element.',
|
|
1588
|
+
fix: "Use an imported binding from 'snice' or 'snice/react', add a side-effect registration import from the documented snice/components path, render a released snice-* element, or remove snice from package.json.",
|
|
1418
1589
|
file: manifest.filename,
|
|
1419
1590
|
line: location.line,
|
|
1420
1591
|
column: location.column
|
|
@@ -1502,13 +1673,20 @@ export function findImports(source) {
|
|
|
1502
1673
|
const statement = source.slice(start.index, start.index + 4000);
|
|
1503
1674
|
const sideEffect = statement.match(/^\s*import\s*(['"])([^'"]+)\1\s*;?/);
|
|
1504
1675
|
if (sideEffect) {
|
|
1505
|
-
imports.push({
|
|
1676
|
+
imports.push({
|
|
1677
|
+
index: start.index,
|
|
1678
|
+
end: start.index + sideEffect[0].length,
|
|
1679
|
+
clause: null,
|
|
1680
|
+
path: sideEffect[2],
|
|
1681
|
+
typeOnly: false
|
|
1682
|
+
});
|
|
1506
1683
|
continue;
|
|
1507
1684
|
}
|
|
1508
1685
|
const from = statement.match(/^\s*import\s+(?:type\s+)?([\s\S]*?)\s+from\s*(['"])([^'"]+)\2\s*;?/);
|
|
1509
1686
|
if (!from || /\n\s*import\b/.test(from[1])) continue;
|
|
1510
1687
|
imports.push({
|
|
1511
1688
|
index: start.index,
|
|
1689
|
+
end: start.index + from[0].length,
|
|
1512
1690
|
clause: from[1].trim(),
|
|
1513
1691
|
path: from[3],
|
|
1514
1692
|
typeOnly: /^\s*import\s+type\b/.test(statement)
|
|
@@ -1549,6 +1727,175 @@ function findClassBodies(source) {
|
|
|
1549
1727
|
return classes;
|
|
1550
1728
|
}
|
|
1551
1729
|
|
|
1730
|
+
function findRouterConstructions(source, routerNames) {
|
|
1731
|
+
const constructions = [];
|
|
1732
|
+
for (const routerName of routerNames) {
|
|
1733
|
+
const escaped = escapeRegExp(routerName);
|
|
1734
|
+
const assigned = new Set();
|
|
1735
|
+
const destructured = new RegExp(
|
|
1736
|
+
`\\b(?:const|let|var)\\s*\\{([^}]*)\\}\\s*=\\s*${escaped}\\s*\\(`,
|
|
1737
|
+
'g'
|
|
1738
|
+
);
|
|
1739
|
+
for (const match of source.matchAll(destructured)) {
|
|
1740
|
+
const initialize = /(?:^|,)\s*initialize(?:\s*:\s*([A-Za-z_$][\w$]*))?(?=\s*,|\s*$)/
|
|
1741
|
+
.exec(match[1]);
|
|
1742
|
+
constructions.push({
|
|
1743
|
+
index: match.index,
|
|
1744
|
+
initializers: initialize ? [initialize[1] ?? 'initialize'] : [],
|
|
1745
|
+
instance: null
|
|
1746
|
+
});
|
|
1747
|
+
assigned.add(source.indexOf(routerName, match.index));
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
const instance = new RegExp(
|
|
1751
|
+
`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*${escaped}\\s*\\(`,
|
|
1752
|
+
'g'
|
|
1753
|
+
);
|
|
1754
|
+
for (const match of source.matchAll(instance)) {
|
|
1755
|
+
const initializers = [];
|
|
1756
|
+
const instanceDestructuring = new RegExp(
|
|
1757
|
+
`\\b(?:export\\s+)?(?:const|let|var)\\s*\\{([^}]*)\\}\\s*=\\s*${escapeRegExp(match[1])}\\b`,
|
|
1758
|
+
'g'
|
|
1759
|
+
);
|
|
1760
|
+
for (const destructuring of source.matchAll(instanceDestructuring)) {
|
|
1761
|
+
const initialize = /(?:^|,)\s*initialize(?:\s*:\s*([A-Za-z_$][\w$]*))?(?=\s*,|\s*$)/
|
|
1762
|
+
.exec(destructuring[1]);
|
|
1763
|
+
if (initialize) initializers.push(initialize[1] ?? 'initialize');
|
|
1764
|
+
}
|
|
1765
|
+
constructions.push({ index: match.index, initializers, instance: match[1] });
|
|
1766
|
+
assigned.add(source.indexOf(routerName, match.index));
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
for (const call of findObjectCalls(source, routerName)) {
|
|
1770
|
+
if (assigned.has(call.index)) continue;
|
|
1771
|
+
constructions.push({ index: call.index, initializers: [], instance: null });
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
return constructions.sort((left, right) => left.index - right.index);
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function hasInvocation(source, name) {
|
|
1778
|
+
const pattern = new RegExp(`\\b${escapeRegExp(name)}\\s*\\(`, 'g');
|
|
1779
|
+
for (const match of source.matchAll(pattern)) {
|
|
1780
|
+
const open = source.indexOf('(', match.index);
|
|
1781
|
+
const close = findMatchingDelimiter(source, open, '(', ')');
|
|
1782
|
+
if (close < 0) continue;
|
|
1783
|
+
let next = close + 1;
|
|
1784
|
+
while (/\s/.test(source[next] ?? '')) next++;
|
|
1785
|
+
// A function or method declaration has a body immediately after its
|
|
1786
|
+
// parameter list. Router initializers are zero-argument function calls.
|
|
1787
|
+
if (source[next] !== '{') return true;
|
|
1788
|
+
}
|
|
1789
|
+
return false;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
function findDecoratedClasses(source, decoratorName) {
|
|
1793
|
+
const declarations = [];
|
|
1794
|
+
const pattern = new RegExp(`@${escapeRegExp(decoratorName)}\\b`, 'g');
|
|
1795
|
+
for (const match of source.matchAll(pattern)) {
|
|
1796
|
+
let declarationStart = match.index + match[0].length;
|
|
1797
|
+
while (/\s/.test(source[declarationStart] ?? '')) declarationStart++;
|
|
1798
|
+
if (source[declarationStart] === '(') {
|
|
1799
|
+
const close = findMatchingDelimiter(source, declarationStart, '(', ')');
|
|
1800
|
+
if (close < 0) continue;
|
|
1801
|
+
declarationStart = close + 1;
|
|
1802
|
+
}
|
|
1803
|
+
// Class decorators stack above the same declaration. Walk past any
|
|
1804
|
+
// intervening decorators so each Snice role resolves to that class.
|
|
1805
|
+
while (true) {
|
|
1806
|
+
while (/\s/.test(source[declarationStart] ?? '')) declarationStart++;
|
|
1807
|
+
const nextDecorator = /^@[A-Za-z_$][\w$]*/.exec(source.slice(declarationStart));
|
|
1808
|
+
if (!nextDecorator) break;
|
|
1809
|
+
declarationStart += nextDecorator[0].length;
|
|
1810
|
+
while (/\s/.test(source[declarationStart] ?? '')) declarationStart++;
|
|
1811
|
+
if (source[declarationStart] === '(') {
|
|
1812
|
+
const close = findMatchingDelimiter(source, declarationStart, '(', ')');
|
|
1813
|
+
if (close < 0) break;
|
|
1814
|
+
declarationStart = close + 1;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
const declaration = /^\s*(?:export\s+)?(?:default\s+)?(?:abstract\s+)?class\s+([A-Za-z_$][\w$]*)[^\{]*\{/
|
|
1818
|
+
.exec(source.slice(declarationStart, declarationStart + 2_000));
|
|
1819
|
+
if (!declaration) continue;
|
|
1820
|
+
const open = declarationStart + declaration.index + declaration[0].lastIndexOf('{');
|
|
1821
|
+
const close = findMatchingDelimiter(source, open, '{', '}');
|
|
1822
|
+
if (close < 0) continue;
|
|
1823
|
+
declarations.push({
|
|
1824
|
+
index: match.index,
|
|
1825
|
+
name: declaration[1],
|
|
1826
|
+
bodyStart: open + 1,
|
|
1827
|
+
body: source.slice(open + 1, close)
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
return declarations;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
function findClassMethods(body, bodyStart = 0) {
|
|
1834
|
+
const code = blankStringContents(body);
|
|
1835
|
+
const depths = new Uint16Array(code.length + 1);
|
|
1836
|
+
let depth = 0;
|
|
1837
|
+
for (let index = 0; index < code.length; index++) {
|
|
1838
|
+
depths[index] = depth;
|
|
1839
|
+
if (code[index] === '{') depth++;
|
|
1840
|
+
else if (code[index] === '}') depth = Math.max(0, depth - 1);
|
|
1841
|
+
}
|
|
1842
|
+
const methods = [];
|
|
1843
|
+
const pattern = /(?:^|\n)\s*(?:(?:public|private|protected|static|readonly|override|abstract|declare)\s+)*(?:async\s+)?(?:\*\s*)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*(?::\s*[^\n{]+)?\s*\{/g;
|
|
1844
|
+
const excluded = new Set(['if', 'for', 'while', 'switch', 'catch', 'with']);
|
|
1845
|
+
for (const match of code.matchAll(pattern)) {
|
|
1846
|
+
const open = match.index + match[0].lastIndexOf('{');
|
|
1847
|
+
if (depths[open] !== 0 || excluded.has(match[1])) continue;
|
|
1848
|
+
const close = findMatchingDelimiter(code, open, '{', '}');
|
|
1849
|
+
if (close < 0) continue;
|
|
1850
|
+
methods.push({
|
|
1851
|
+
name: match[1],
|
|
1852
|
+
index: bodyStart + match.index + match[0].indexOf(match[1]),
|
|
1853
|
+
body: body.slice(open + 1, close)
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
return methods;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
function pageLogicProfile(body) {
|
|
1860
|
+
const code = blankStringContents(body);
|
|
1861
|
+
const methods = findClassMethods(body);
|
|
1862
|
+
const effectPattern = /\bfetch\s*\(|\bnew\s+(?:WebSocket|EventSource|Worker)\s*\(|\b(?:localStorage|sessionStorage|indexedDB|caches)\b|\b(?:setInterval|setTimeout)\s*\(/g;
|
|
1863
|
+
const effectTest = /\bfetch\s*\(|\bnew\s+(?:WebSocket|EventSource|Worker)\s*\(|\b(?:localStorage|sessionStorage|indexedDB|caches)\b|\b(?:setInterval|setTimeout)\s*\(/;
|
|
1864
|
+
const decisionPattern = /\b(?:if|else\s+if|switch|for|while|try|catch)\b|\.(?:filter|map|reduce|sort|find|some|every)\s*\(/g;
|
|
1865
|
+
const effects = [...code.matchAll(effectPattern)].length;
|
|
1866
|
+
const decisions = [...code.matchAll(decisionPattern)].length;
|
|
1867
|
+
const effectfulMethods = methods.filter(method => {
|
|
1868
|
+
const methodCode = blankStringContents(method.body);
|
|
1869
|
+
return effectTest.test(methodCode);
|
|
1870
|
+
}).length;
|
|
1871
|
+
// A page is supposed to orchestrate. Recommend extraction only when several
|
|
1872
|
+
// separate methods own substantial external/business behavior, or when a
|
|
1873
|
+
// very large page also carries a dense decision tree. One fetch in @ready()
|
|
1874
|
+
// or a small route-state branch remains clean.
|
|
1875
|
+
const recommend = (
|
|
1876
|
+
(effectfulMethods >= 4 && effects >= 6) ||
|
|
1877
|
+
(effectfulMethods >= 3 && effects >= 4 && decisions >= 8) ||
|
|
1878
|
+
(code.length >= 4_000 && effects >= 2 && decisions >= 16)
|
|
1879
|
+
);
|
|
1880
|
+
return { recommend, effectfulMethods, effects, decisions };
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
function sharedLogicFingerprint(body) {
|
|
1884
|
+
const code = blankStringContents(maskComments(body))
|
|
1885
|
+
.replace(/\s+/g, ' ')
|
|
1886
|
+
.trim();
|
|
1887
|
+
if (code.length < 140 || (code.match(/;/g) ?? []).length < 3) return null;
|
|
1888
|
+
if (!/\bfetch\s*\(|\bnew\s+(?:WebSocket|EventSource|Worker)\s*\(|\b(?:localStorage|sessionStorage|indexedDB)\b|\b(?:if|for|while|switch)\b|\.(?:filter|map|reduce|sort)\s*\(/.test(code)) {
|
|
1889
|
+
return null;
|
|
1890
|
+
}
|
|
1891
|
+
return code;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
function isInSourceFolder(filename, folder) {
|
|
1895
|
+
const normalized = filename.replaceAll('\\', '/');
|
|
1896
|
+
return new RegExp(`(?:^|/)src/${escapeRegExp(folder)}(?:/|$)`).test(normalized);
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1552
1899
|
function findMatchingDelimiter(source, start, open, close) {
|
|
1553
1900
|
let depth = 0;
|
|
1554
1901
|
let quote = '';
|
|
@@ -1761,6 +2108,120 @@ export function isTypeOnlyImport(entry) {
|
|
|
1761
2108
|
return specifiers.length > 0 && specifiers.every(specifier => /^type\s+/.test(specifier));
|
|
1762
2109
|
}
|
|
1763
2110
|
|
|
2111
|
+
function importHasProjectUse(source, entry) {
|
|
2112
|
+
// Side-effect imports are themselves runtime usage (and are the documented
|
|
2113
|
+
// way to register individual Snice custom elements).
|
|
2114
|
+
if (!entry.clause) return true;
|
|
2115
|
+
|
|
2116
|
+
const namespace = entry.clause.match(/\*\s+as\s+([A-Za-z_$][\w$]*)/);
|
|
2117
|
+
// The observed gauntlet miss is specifically a cargo-culted namespace
|
|
2118
|
+
// import. Named/default import liveness belongs to TypeScript or a linter;
|
|
2119
|
+
// broadening this project rule into an incomplete identifier-use checker
|
|
2120
|
+
// would create false positives for otherwise valid authored imports.
|
|
2121
|
+
if (!namespace) return true;
|
|
2122
|
+
|
|
2123
|
+
const outsideImport = `${source.slice(0, entry.index)}${' '.repeat(entry.end - entry.index)}${source.slice(entry.end)}`;
|
|
2124
|
+
return hasCodeIdentifier(outsideImport, namespace[1]);
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
function hasCodeIdentifier(source, target) {
|
|
2128
|
+
const stack = [{ kind: 'code', templateExpression: false, braces: 0 }];
|
|
2129
|
+
let index = 0;
|
|
2130
|
+
while (index < source.length) {
|
|
2131
|
+
const frame = stack.at(-1);
|
|
2132
|
+
const character = source[index];
|
|
2133
|
+
|
|
2134
|
+
if (frame.kind === 'quoted') {
|
|
2135
|
+
if (character === '\\') index += 2;
|
|
2136
|
+
else {
|
|
2137
|
+
index++;
|
|
2138
|
+
if (character === frame.quote) stack.pop();
|
|
2139
|
+
}
|
|
2140
|
+
continue;
|
|
2141
|
+
}
|
|
2142
|
+
if (frame.kind === 'regex') {
|
|
2143
|
+
if (character === '\\') {
|
|
2144
|
+
index += 2;
|
|
2145
|
+
} else if (character === '[') {
|
|
2146
|
+
frame.characterClass = true;
|
|
2147
|
+
index++;
|
|
2148
|
+
} else if (character === ']' && frame.characterClass) {
|
|
2149
|
+
frame.characterClass = false;
|
|
2150
|
+
index++;
|
|
2151
|
+
} else if (character === '/' && !frame.characterClass) {
|
|
2152
|
+
index++;
|
|
2153
|
+
while (/[A-Za-z]/.test(source[index] ?? '')) index++;
|
|
2154
|
+
stack.pop();
|
|
2155
|
+
} else {
|
|
2156
|
+
index++;
|
|
2157
|
+
}
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
if (frame.kind === 'template') {
|
|
2161
|
+
if (character === '\\') {
|
|
2162
|
+
index += 2;
|
|
2163
|
+
} else if (character === '`') {
|
|
2164
|
+
stack.pop();
|
|
2165
|
+
index++;
|
|
2166
|
+
} else if (character === '$' && source[index + 1] === '{') {
|
|
2167
|
+
stack.push({ kind: 'code', templateExpression: true, braces: 1 });
|
|
2168
|
+
index += 2;
|
|
2169
|
+
} else {
|
|
2170
|
+
index++;
|
|
2171
|
+
}
|
|
2172
|
+
continue;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
if (character === "'" || character === '"') {
|
|
2176
|
+
stack.push({ kind: 'quoted', quote: character });
|
|
2177
|
+
index++;
|
|
2178
|
+
continue;
|
|
2179
|
+
}
|
|
2180
|
+
if (character === '`') {
|
|
2181
|
+
stack.push({ kind: 'template' });
|
|
2182
|
+
index++;
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
if (character === '/' && startsRegexLiteral(source, index)) {
|
|
2186
|
+
stack.push({ kind: 'regex', characterClass: false });
|
|
2187
|
+
index++;
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
if (frame.templateExpression && character === '{') {
|
|
2191
|
+
frame.braces++;
|
|
2192
|
+
index++;
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
if (frame.templateExpression && character === '}') {
|
|
2196
|
+
frame.braces--;
|
|
2197
|
+
index++;
|
|
2198
|
+
if (frame.braces === 0) stack.pop();
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
if (/[A-Za-z_$]/.test(character)) {
|
|
2202
|
+
let end = index + 1;
|
|
2203
|
+
while (/[\w$]/.test(source[end] ?? '')) end++;
|
|
2204
|
+
if (source.slice(index, end) === target) return true;
|
|
2205
|
+
index = end;
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
index++;
|
|
2209
|
+
}
|
|
2210
|
+
return false;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
function startsRegexLiteral(source, slashIndex) {
|
|
2214
|
+
let previous = slashIndex - 1;
|
|
2215
|
+
while (previous >= 0 && /\s/.test(source[previous])) previous--;
|
|
2216
|
+
if (previous < 0 || /[([{=:;,!?&|+*%^~<>-]/.test(source[previous])) return true;
|
|
2217
|
+
if (!/[\w$]/.test(source[previous])) return false;
|
|
2218
|
+
let start = previous;
|
|
2219
|
+
while (start > 0 && /[\w$]/.test(source[start - 1])) start--;
|
|
2220
|
+
return /^(?:return|case|throw|else|do|typeof|instanceof|in|of|yield|await|void|delete)$/.test(
|
|
2221
|
+
source.slice(start, previous + 1)
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
|
|
1764
2225
|
function reactExportReplacement(name) {
|
|
1765
2226
|
if (name === 'SniceRoute') return 'Route';
|
|
1766
2227
|
if (name === 'SniceApp') return 'SniceProvider (or SniceRouter)';
|
package/bin/snice.js
CHANGED
|
@@ -49,6 +49,9 @@ Commands:
|
|
|
49
49
|
generate-component Print a current Snice element scaffold (use --out to write it).
|
|
50
50
|
build-component Build a CDN component from a Snice source checkout.
|
|
51
51
|
|
|
52
|
+
AI setup:
|
|
53
|
+
npx snice init-ai Install version-matched guidance before an agent writes Snice code.
|
|
54
|
+
|
|
52
55
|
Build options:
|
|
53
56
|
--output=<dir> Output directory (default: ./dist/cdn)
|
|
54
57
|
--format=iife,es Output formats (default: iife; table: iife,es)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { getContext } from './context-provider';
|
|
2
|
+
import type { AppContext } from './types/app-context';
|
|
3
|
+
/**
|
|
4
|
+
* Makes one explicit application context visible beneath a root.
|
|
5
|
+
*
|
|
6
|
+
* Daemon instances must already exist in context.daemons. The returned cleanup
|
|
7
|
+
* removes the provider and deactivates framework communication for daemons no
|
|
8
|
+
* longer provided by any context.
|
|
9
|
+
*/
|
|
10
|
+
export declare function provideContext(root: EventTarget, context: AppContext): () => void;
|
|
11
|
+
export { getContext };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* snice v7.
|
|
3
|
-
* A decorator-driven web component library with
|
|
2
|
+
* snice v7.1.1
|
|
3
|
+
* A decorator-driven web component library with routing, controllers, daemons, and 130+ UI components. For better coding-agent results, run npx snice init-ai.
|
|
4
4
|
* (c) 2024
|
|
5
5
|
* Released under the MIT License.
|
|
6
6
|
*/
|