@bakery-framework/core 2.0.1 → 2.0.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakery-framework/core",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Bakery framework core: handlers, router, config, session, caches, logger, compiler.",
5
5
  "keywords": [
6
6
  "bakery",
@@ -54,19 +54,27 @@ function buildPaths(): MapOf<string[]> {
54
54
  const PROJECT_DIR = fs.resolve(APP_DIR, '.cache/tsconfig')
55
55
 
56
56
  /**
57
- * The two projects core always generates.
57
+ * The three projects core always generates.
58
58
  *
59
- * The split is the whole point: only `server` carries `bun-types`, so `Bun.*`
60
- * in a file bound for the browser is a type error rather than a runtime one.
61
- * Before this existed, one config covered everything and `Bun.hash()` in a
62
- * client file typechecked clean and failed in the browser.
59
+ * The split is the whole point: `server` and `api` carry `bun-types` and
60
+ * `client` does not, so `Bun.*` in a file bound for the browser is a type
61
+ * error rather than a runtime one. Before this existed, one config covered
62
+ * everything and `Bun.hash()` in a client file typechecked clean and failed
63
+ * in the browser.
63
64
  *
64
- * The includes overlap the app's own tsconfig, and that is fine *because
65
- * nothing references these projects*: each is a standalone projection of one
66
- * concern, pointed at directly (`tsc -p .cache/tsconfig/client.json`), and
67
- * subtracting the app's include would gut them into projects that check
68
- * nothing. What must never come back is the `references` wiring that composed
69
- * them with the app project. See {@link syncTSConfigProjects}.
65
+ * **Between them they claim every app file once**, because the app's root
66
+ * tsconfig claims none: it reaches these projects through
67
+ * `tsconfig.bakery.json` (see {@link syncTSConfigProjects}), and an editor
68
+ * gives a file to the first project that claims it. A file nobody claims
69
+ * falls into an inferred project with default options and no `bun-types`,
70
+ * which is how every `api/` file in a real app reported "Cannot find name
71
+ * 'Bun'". So `server` is defined by what it is not: everything outside the
72
+ * serve root (config, schema, `orm/`, `scripts/`, `tests/`, migrations) plus
73
+ * every `.tsx` page, which is server-rendered. `api` is its own project only
74
+ * because a glob cannot say "`src/**` except `api/`" in either direction.
75
+ *
76
+ * Naming an `exclude` drops TypeScript's default one, so `node_modules` and
77
+ * `.cache` (compiled server modules live there as `.ts`) are named again.
70
78
  *
71
79
  * Globs are app-relative here and rewritten to be relative to the generated
72
80
  * file, which sits two levels down.
@@ -86,13 +94,14 @@ export function coreProjects(): PluginTsProject[] {
86
94
  jsxFactory: 'createElement',
87
95
  jsxFragmentFactory: 'Fragment',
88
96
  },
89
- include: [
90
- `${root}/**/api/**/*.ts`,
91
- `${root}/**/*.tsx`,
92
- 'server.config.ts',
93
- 'schema.ts',
94
- 'orm/**/*.ts',
95
- ],
97
+ include: ['**/*.ts', '**/*.tsx'],
98
+ exclude: [`${root}/**/*.ts`, 'node_modules', '.cache'],
99
+ },
100
+ {
101
+ name: 'api',
102
+ server: true,
103
+ extends: '@bakery-framework/core/tsconfig.server.json',
104
+ include: [`${root}/**/api/**/*.ts`],
96
105
  },
97
106
  {
98
107
  name: 'client',
@@ -365,9 +374,15 @@ function isGeneratedReference(entry: unknown): boolean {
365
374
  * `allowImportingTsExtensions`, so they are unbuildable by design. Making them
366
375
  * `composite` instead would trade the errors above for a `tsc -b` build-order
367
376
  * requirement no consumer runs, and TS6305 would still fire for any root file
368
- * importing into one while unbuilt. The only reference shape `tsc -p`
369
- * tolerates from a root that has files of its own is no reference at all, so
370
- * the projects stand alone now and this strips what previous releases wrote.
377
+ * importing into one while unbuilt.
378
+ *
379
+ * **A root that has files of its own tolerates no reference into them,
380
+ * direct or chained.** Routed through a `composite`, `files: []` middle
381
+ * config, TS6306 and TS6310 go away and TS6305 does not: measured on a real
382
+ * app whose root claimed `src/**`, 57 of them. What works is the other
383
+ * direction, a root that claims nothing and reaches the projects through
384
+ * `tsconfig.bakery.json` (see {@link wireRoot}). This strips the direct
385
+ * entries previous releases wrote, which is still a repair worth making.
371
386
  *
372
387
  * Everything the developer owns is preserved: only entries into
373
388
  * `.cache/tsconfig/` are removed, a real project reference (say `../shared`)
@@ -394,6 +409,224 @@ export function stripGeneratedReferences(
394
409
  return repaired
395
410
  }
396
411
 
412
+ /** The generated file the root config references, and how it is spelled there. */
413
+ const CHAIN_FILE = 'tsconfig.bakery.json'
414
+ const CHAIN_PATH = fs.resolve(APP_DIR, CHAIN_FILE)
415
+ const CHAIN_REF = `./${CHAIN_FILE}`
416
+
417
+ /**
418
+ * `tsconfig.bakery.json`: every generated project, listed for an editor.
419
+ *
420
+ * **The middle layer is what makes the chain legal.** The root references
421
+ * this file, and this file references `.cache/tsconfig/*`. A root referencing
422
+ * those projects directly gets TS6306 and TS6310 for pointing at
423
+ * non-composite, `noEmit` projects. A `composite` config with `files: []` in
424
+ * between is a valid target, and tsserver follows references through it.
425
+ * Measured on a real app with TypeScript 5.9.3: every `api/` file moved from
426
+ * an inferred project with no `bun-types` to the server-side project, and
427
+ * every other `.ts` file to `client`.
428
+ *
429
+ * The server owns this file. A plugin contributes a project through
430
+ * `tsconfig.project` and the generator lists it here, so a plugin never writes
431
+ * it, and a project that failed to write is not listed.
432
+ */
433
+ export function chainConfig(names: string[]): Record<string, unknown> {
434
+ return {
435
+ $comment:
436
+ 'GENERATED by Bakery on dev boot and by `bakery --types`. Edits are lost. tsconfig.json references this file, and it references each project in .cache/tsconfig/, which is how an editor gives a file its scope.',
437
+ compilerOptions: { composite: true },
438
+ files: [],
439
+ references: names.map(name => ({ path: `./.cache/tsconfig/${name}.json` })),
440
+ }
441
+ }
442
+
443
+ /**
444
+ * Write the chain file when its content changes, and say whether it did.
445
+ *
446
+ * A boot that rewrites an unchanged file dirties git every time and trains
447
+ * people to ignore the diff. The answer also decides when the advice for a
448
+ * root that claims files is worth giving (see {@link syncTSConfigProjects}).
449
+ */
450
+ async function writeChain(names: string[]): Promise<boolean> {
451
+ const next = `${JSON.stringify(chainConfig(names), null, 2)}\n`
452
+ const current = fs.exists(CHAIN_PATH)
453
+ ? await Bun.file(CHAIN_PATH).text()
454
+ : null
455
+ if (current === next) return false
456
+ await Bun.write(CHAIN_PATH, next)
457
+ return true
458
+ }
459
+
460
+ /** Whether a root config already references the chain file, however spelled. */
461
+ export function referencesChain(root: Record<string, unknown>): boolean {
462
+ const refs = root.references
463
+ if (!Array.isArray(refs)) return false
464
+ return refs.some(entry => {
465
+ const path = (entry as { path?: unknown } | null)?.path
466
+ return (
467
+ typeof path === 'string' &&
468
+ path.replace(/\\/g, '/').replace(RE_LEADING_SLASHES, '') === CHAIN_FILE
469
+ )
470
+ })
471
+ }
472
+
473
+ /**
474
+ * The config a root `extends`, read one level deep, or `null`.
475
+ *
476
+ * Only for {@link claimsAppFiles}: a root with no `files` or `include` of its
477
+ * own inherits the base's, and core's server config lists the three ambient
478
+ * declarations, which is what keeps such a root from claiming its whole
479
+ * directory.
480
+ */
481
+ function baseConfigOf(
482
+ root: Record<string, unknown>,
483
+ ): Record<string, unknown> | null {
484
+ if (typeof root.extends !== 'string') return null
485
+ try {
486
+ const base = Bun.resolveSync(root.extends, APP_DIR)
487
+ return parseJSONC(readFileSync(base, 'utf8'))
488
+ } catch {
489
+ // Unreadable reads as "no base", which `claimsAppFiles` takes as
490
+ // TypeScript's default of claiming everything: the root is then left
491
+ // unwired, which is the direction that cannot break `tsc -p`.
492
+ return null
493
+ }
494
+ }
495
+
496
+ /**
497
+ * Whether a root config claims app files for itself.
498
+ *
499
+ * **A root that does cannot be wired to the chain.** Each file it shares with
500
+ * a generated project is redirected to that project's never-built output, and
501
+ * `tsc -p` fails with TS6305 once per file: 57 on the app this was measured
502
+ * on. Declaration files are not redirected, so a root claiming only ambient
503
+ * `.d.ts` files, which is what it inherits from core's server config, claims
504
+ * nothing that matters.
505
+ *
506
+ * TypeScript's rules, in order: the root's own `include` and `files` win over
507
+ * the base's; `files` without `include` claims only those files; and neither,
508
+ * anywhere, claims every file under the directory.
509
+ */
510
+ export function claimsAppFiles(
511
+ root: Record<string, unknown>,
512
+ base: Record<string, unknown> | null,
513
+ ): boolean {
514
+ const pick = (key: 'include' | 'files') =>
515
+ key in root ? root[key] : base?.[key]
516
+ const include = pick('include')
517
+ const files = pick('files')
518
+
519
+ if (Array.isArray(include) && include.length > 0) return true
520
+ if (
521
+ Array.isArray(files) &&
522
+ files.some(f => typeof f === 'string' && !f.endsWith('.d.ts'))
523
+ ) {
524
+ return true
525
+ }
526
+ return include === undefined && files === undefined
527
+ }
528
+
529
+ /**
530
+ * Add the chain reference to a root with no `references` key, as text.
531
+ *
532
+ * **Textual, so the developer's comments survive.** `tsconfig.json` is JSONC
533
+ * and theirs, and a parse-and-stringify rewrite drops every comment in it.
534
+ * This finds the last member of the top-level object, skipping strings and
535
+ * comments, and adds one member after it, with a comma unless one is already
536
+ * there. `null` when the text has no top-level object to extend.
537
+ */
538
+ export function insertChainReference(text: string): string | null {
539
+ const eol = text.includes('\r\n') ? '\r\n' : '\n'
540
+ let depth = 0
541
+ let inString = false
542
+ let escaped = false
543
+ let last = -1
544
+ let close = -1
545
+
546
+ for (let i = 0; i < text.length; i++) {
547
+ const c = text[i]
548
+ if (inString) {
549
+ if (escaped) escaped = false
550
+ else if (c === '\\') escaped = true
551
+ else if (c === '"') inString = false
552
+ last = i
553
+ continue
554
+ }
555
+ if (c === '/' && text[i + 1] === '/') {
556
+ const end = text.indexOf('\n', i)
557
+ i = end === -1 ? text.length : end
558
+ continue
559
+ }
560
+ if (c === '/' && text[i + 1] === '*') {
561
+ const end = text.indexOf('*/', i + 2)
562
+ i = end === -1 ? text.length : end + 1
563
+ continue
564
+ }
565
+ if (c === '"') {
566
+ inString = true
567
+ last = i
568
+ continue
569
+ }
570
+ if (c === ' ' || c === '\t' || c === '\n' || c === '\r') continue
571
+ if (c === '{' || c === '[') depth++
572
+ else if (c === '}' || c === ']') {
573
+ depth--
574
+ if (depth === 0 && c === '}') {
575
+ close = i
576
+ break
577
+ }
578
+ }
579
+ last = i
580
+ }
581
+
582
+ if (close === -1 || last === -1) return null
583
+
584
+ const comma = text[last] === '{' || text[last] === ',' ? '' : ','
585
+ const rest = text.slice(last + 1)
586
+ const gap = rest.startsWith('\n') || rest.startsWith('\r\n') ? '' : eol
587
+ return `${text.slice(0, last + 1)}${comma}${eol} "references": [{ "path": "${CHAIN_REF}" }]${gap}${rest}`
588
+ }
589
+
590
+ /**
591
+ * What the root config should become, as text, or `null` for no write.
592
+ *
593
+ * Two repairs, in order. The direct references earlier releases wrote are
594
+ * stripped ({@link stripGeneratedReferences}). Then the chain reference is
595
+ * added, unless the root already has it or claims app files of its own, in
596
+ * which case adding it would break `tsc -p` ({@link claimsAppFiles}) and the
597
+ * caller says so instead.
598
+ *
599
+ * Pure, and the place the rule "a root never references `.cache/tsconfig/`"
600
+ * is held: the only entry it ever adds is the chain file.
601
+ */
602
+ export function wireRoot(
603
+ text: string,
604
+ base: Record<string, unknown> | null,
605
+ ): { text: string | null; stripped: boolean; wired: boolean; claims: boolean } {
606
+ let root = parseJSONC(text) as Record<string, unknown>
607
+ let out: string | null = null
608
+
609
+ const repaired = stripGeneratedReferences(root)
610
+ if (repaired) {
611
+ root = repaired
612
+ out = `${JSON.stringify(repaired, null, 2)}\n`
613
+ }
614
+ const stripped = repaired !== null
615
+
616
+ if (referencesChain(root)) {
617
+ return { text: out, stripped, wired: false, claims: false }
618
+ }
619
+ if (claimsAppFiles(root, base)) {
620
+ return { text: out, stripped, wired: false, claims: true }
621
+ }
622
+
623
+ const refs = Array.isArray(root.references) ? root.references : null
624
+ const wired =
625
+ (refs ? null : insertChainReference(out ?? text)) ??
626
+ `${JSON.stringify({ ...root, references: [...(refs ?? []), { path: CHAIN_REF }] }, null, 2)}\n`
627
+ return { text: wired, stripped, wired: true, claims: false }
628
+ }
629
+
397
630
  /**
398
631
  * The root config written when the app has none at all.
399
632
  *
@@ -402,6 +635,10 @@ export function stripGeneratedReferences(
402
635
  * only into a relative path, never a package specifier, so the file this
403
636
  * writes has to carry the JSX options itself, inline, exactly as the
404
637
  * scaffolder spells them.
638
+ *
639
+ * It claims no app files: it inherits only core's ambient declarations, and
640
+ * reaches everything else through the chain, which is the one shape the
641
+ * chain is legal in (see {@link claimsAppFiles}).
405
642
  */
406
643
  export function defaultRootConfig(): Record<string, unknown> {
407
644
  return {
@@ -411,50 +648,59 @@ export function defaultRootConfig(): Record<string, unknown> {
411
648
  jsxFactory: 'createElement',
412
649
  jsxFragmentFactory: 'Fragment',
413
650
  },
651
+ references: [{ path: CHAIN_REF }],
414
652
  }
415
653
  }
416
654
 
417
655
  /**
418
- * Generate the project configs, and keep the app's root tsconfig viable:
419
- * created with the runtime JSX options when the app has none, and stripped of
420
- * the `references` a previous release wrote into it.
656
+ * Generate the scope projects, list them in `tsconfig.bakery.json`, and wire
657
+ * the app's root tsconfig to that file.
421
658
  *
422
659
  * Separate from `syncTSConfigPaths` because an app can reasonably want one and
423
660
  * not the other: the paths sync has existed for a long time and rewrites a file
424
661
  * people keep in git, while this owns a directory nobody edits.
425
662
  *
426
- * **The generated projects are standalone on purpose; the root config does not
427
- * reference them.** They exist for direct invocation against the one concern
428
- * each covers: `vue-tsc -p .cache/tsconfig/vue.json` is the only way an SFC
429
- * typechecks at all, `tsc -p .cache/tsconfig/client.json` proves browser code
430
- * clean of `Bun.*`, and a plugin's project carries its own ambients the same
431
- * way. Their `include` deliberately overlaps the app's own project: they are
432
- * alternate projections of the same files, used *instead of* the root for
433
- * their slice, never composed with it. Wiring them in as `references` broke
434
- * `tsc -p <app>` for any consumer who had booted once (TS6305/6306/6310: the
435
- * measurements are on {@link stripGeneratedReferences}), and what the wiring
436
- * bought was less than it looked: tsserver routes a file to the project whose
437
- * `include` claims it, so for everything a scaffolded root claims, `src/**`,
438
- * `server.config.ts`, `orm/**`, the reference walk never ran anyway.
663
+ * **Who owns what.** `.cache/tsconfig/*.json` and `tsconfig.bakery.json` are
664
+ * generated, and edits to them are lost. `tsconfig.json` is the developer's:
665
+ * the only change made to it is the one `references` entry for the chain,
666
+ * inserted as text so its comments survive, plus the one-time strip of the
667
+ * direct references earlier releases wrote.
668
+ *
669
+ * **Why the chain.** An editor gives a file to the project that claims it.
670
+ * With the projects standing alone, nothing pointed an editor at them, so a
671
+ * root that claimed nothing left every app file in an inferred project, and
672
+ * `Bun` was unknown in every `api/` file. Through the chain each file gets its
673
+ * scope: `api/` and pages to server-side projects, other `.ts` to `client`, an
674
+ * SFC to plugin-vue's project. `tsc -b` checks all of them at once.
675
+ *
676
+ * **Only for a root that claims no app files.** One that does fails
677
+ * `tsc -p` with TS6305 once per shared file when chained, so it is left as it
678
+ * is, with a line saying how to move ({@link claimsAppFiles}). Said when the
679
+ * chain file changes rather than on every boot, since keeping the old shape
680
+ * can be deliberate.
681
+ *
682
+ * **A fresh clone has none of these files** until something generates them,
683
+ * and `tsc` then stops at TS6053 on the chain's references. `bakery --types`
684
+ * generates them without starting a server, so a clone, a CI job or a
685
+ * `typecheck` script can run it first. The cache wipe keeps
686
+ * `.cache/tsconfig/` for the same reason (`WIPE_KEEP` in `cache-version.ts`).
439
687
  *
440
688
  * Two earlier lessons still bind the root-config half. It used to be
441
689
  * *replaced* with a references-only stub, which silently broke every `.tsx`
442
690
  * page: Bun's runtime reads `compilerOptions.jsx*` from the root and does not
443
691
  * follow `references`, so pages transpiled against the automatic JSX runtime
444
692
  * and `GET /` answered 200 with a JSON-encoded React element tree. Hence
445
- * {@link defaultRootConfig} when no root exists, and surgical repair (never
693
+ * {@link defaultRootConfig} when no root exists, and surgical edits (never a
446
694
  * wholesale rewrite) when one does. And a boot that dirties git every time
447
- * trains people to ignore the diff, so an already-clean root is not rewritten.
695
+ * trains people to ignore the diff, so a root already in shape is not
696
+ * rewritten, and neither is an unchanged chain file.
448
697
  */
449
698
  export async function syncTSConfigProjects(): Promise<void> {
450
699
  try {
451
700
  const written = await writeProjects(buildPaths())
701
+ const chainChanged = await writeChain(written)
452
702
 
453
- const current = fs.exists(APP_CONFIG_PATH)
454
- ? parseJSONC(await Bun.file(APP_CONFIG_PATH).text())
455
- : null
456
-
457
- if (!current) {
703
+ if (!fs.exists(APP_CONFIG_PATH)) {
458
704
  await Bun.write(
459
705
  APP_CONFIG_PATH,
460
706
  `${JSON.stringify(defaultRootConfig(), null, 2)}\n`,
@@ -463,11 +709,13 @@ export async function syncTSConfigProjects(): Promise<void> {
463
709
  return
464
710
  }
465
711
 
466
- const repaired = stripGeneratedReferences(current)
467
- if (!repaired) return
712
+ const text = await Bun.file(APP_CONFIG_PATH).text()
713
+ const plan = wireRoot(text, baseConfigOf(parseJSONC(text)))
468
714
 
469
- await Bun.write(APP_CONFIG_PATH, `${JSON.stringify(repaired, null, 2)}\n`)
470
- serveLog.TSCONFIG_REFERENCES_REMOVED()
715
+ if (plan.text !== null) await Bun.write(APP_CONFIG_PATH, plan.text)
716
+ if (plan.stripped) serveLog.TSCONFIG_REFERENCES_REMOVED()
717
+ if (plan.wired) serveLog.TSCONFIG_CHAIN_WIRED()
718
+ if (plan.claims && chainChanged) serveLog.TSCONFIG_ROOT_CLAIMS_FILES()
471
719
  } catch (err: any) {
472
720
  serveLog.UNHANDLED_ERR({
473
721
  error: `TSConfig project sync error: ${errorMsg(err)}`,
@@ -153,12 +153,13 @@ export const __wipeCacheDir = wipe
153
153
  /**
154
154
  * The one thing in `.cache/` the wipe must not take.
155
155
  *
156
- * **The app's committed `tsconfig.json` *references* `.cache/tsconfig/*.json`,
157
- * so deleting them breaks the editor for the whole project**, not one setting,
158
- * everything. TypeScript reports `TS6053: File '…/server.json' not found` for
159
- * each reference, has no project left to put a file in, and falls back to an
160
- * inferred one with no ambients: `req.session`, `Bakery`, the JSX namespace and
161
- * the app's own schema types all stop resolving at once.
156
+ * **The app's `tsconfig.json` reaches `.cache/tsconfig/*.json` through
157
+ * `tsconfig.bakery.json`, so deleting them breaks the editor for the whole
158
+ * project**, not one setting, everything. TypeScript reports
159
+ * `TS6053: File '…/server.json' not found` for each reference, has no project
160
+ * left to put a file in, and falls back to an inferred one with no ambients:
161
+ * `Bun`, `req.session`, `Bakery`, the JSX namespace and the app's own schema
162
+ * types all stop resolving at once.
162
163
  *
163
164
  * That happens on every framework upgrade, because the version wipe is keyed on
164
165
  * the framework version among others. The developer sees their editor lose every
@@ -26,13 +26,21 @@ const serveMsgs = {
26
26
  TSCONFIG_SYNCED:
27
27
  'I Synced %ytsconfig.json%* paths with %yserver.config.ts%*!',
28
28
  TSCONFIG_PROJECTS_WRITTEN:
29
- 'I Wrote %y{count}%* tsconfig project(s) to %y.cache/tsconfig/%*',
29
+ 'I Wrote %y{count}%* tsconfig project(s) to %y.cache/tsconfig/%*, listed in %ytsconfig.bakery.json%*, and a %ytsconfig.json%* that references it',
30
30
  // One-time repair. Previous releases wired the generated projects into the
31
31
  // app's tsconfig.json as `references`, which broke `tsc -p <app>`
32
32
  // (TS6305/6306/6310). Named so the rewrite of a tracked file comes with a
33
33
  // line saying why it happened.
34
34
  TSCONFIG_REFERENCES_REMOVED:
35
- 'I Removed generated %yreferences%* from %ytsconfig.json%*, the %y.cache/tsconfig/%* projects are standalone, and referencing them broke %ytsc -p%*',
35
+ 'I Removed the direct %y.cache/tsconfig/%* entries from %yreferences%* in %ytsconfig.json%*: referencing those projects directly broke %ytsc -p%*, and %ytsconfig.bakery.json%* reaches them instead',
36
+ // The one edit made to a developer's tsconfig.json, named for the same
37
+ // reason as the repair above: a tracked file changed on boot.
38
+ TSCONFIG_CHAIN_WIRED:
39
+ 'I Added %y./tsconfig.bakery.json%* to %yreferences%* in %ytsconfig.json%*: an editor now gives each file its scope project',
40
+ // Said when tsconfig.bakery.json is new or changed, not on every boot: a root
41
+ // that claims files may be kept that way on purpose, and it still works.
42
+ TSCONFIG_ROOT_CLAIMS_FILES:
43
+ 'W %ytsconfig.json%* claims app files, so it is not wired to %ytsconfig.bakery.json%*: chained, it would fail %ytsc -p%* with TS6305. To give each file its scope, remove its %yinclude%* and %yfiles%*, reference %y./tsconfig.bakery.json%*, and typecheck with %ytsc -b%*',
36
44
  // A plugin asking for a project name that is taken. Named rather than
37
45
  // silent: the symptom otherwise is one plugin's types quietly not applying,
38
46
  // discovered much later and blamed on the wrong thing.