tkeron 5.3.0 → 6.0.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.
@@ -0,0 +1,259 @@
1
+ ---
2
+ name: tkeron-troubleshooting
3
+ description: "Troubleshooting tkeron builds and the dev server: common errors with cause-and-fix (Bundle failed, Cannot resolve, missing hyphen, component not replaced, build hangs, websrc not found), dev server singleton recovery after a crash, debugging workflow (read web/, isolate the offending file, check iteration limits), and a quick diagnostic flowchart. Use this skill when a build fails, a component is not inlined, the dev server is down, or output looks wrong."
4
+ ---
5
+
6
+ # Tkeron — Troubleshooting
7
+
8
+ ## Diagnostic Flowchart
9
+
10
+ ```
11
+ Something is wrong
12
+
13
+ ├─ Build fails with an error message? → "Common Build Errors" section
14
+ ├─ Build succeeds but output is wrong? → "Output Looks Wrong" section
15
+ ├─ Dev server is down / hot reload not working? → "Dev Server Recovery" section
16
+ ├─ Test fails? → see tkeron-testing → "Common Test Failures"
17
+ └─ Type error in IDE only? → "IDE vs Build" section
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Common Build Errors
23
+
24
+ ### `Bundle failed` / `Cannot resolve "./index.js"`
25
+
26
+ **Cause**: HTML references `.js` but the source is `.ts`.
27
+
28
+ ```html
29
+ <!-- ❌ -->
30
+ <script src="index.js"></script>
31
+
32
+ <!-- ✅ -->
33
+ <script type="module" src="./index.ts"></script>
34
+ ```
35
+
36
+ ### `Cannot resolve "/styles.css"`
37
+
38
+ **Cause**: absolute path. Bun resolves from the source file, not from the server root.
39
+
40
+ ```html
41
+ <!-- ❌ -->
42
+ <link rel="stylesheet" href="/styles.css" />
43
+ <!-- ✅ -->
44
+ <link rel="stylesheet" href="./styles.css" />
45
+ ```
46
+
47
+ ### `Component name must contain hyphen`
48
+
49
+ **Cause**: a `.com.html` / `.com.ts` / `.com.md` whose base name has no hyphen, or collides with a standard HTML tag.
50
+
51
+ Fix: rename to kebab-case with a hyphen: `card.com.html` → `info-card.com.html`. See `tkeron-organization` for naming rules.
52
+
53
+ ### `Source directory websrc does not exist`
54
+
55
+ Fix: run `tk init .` to scaffold, or `mkdir websrc` and create at least an `index.html`.
56
+
57
+ ### `Skill files already exist: ...` (from `tk skills`)
58
+
59
+ Fix: pass `--force` to overwrite, or delete the existing skill folder first.
60
+
61
+ ### `Skills directory not found in tkeron installation`
62
+
63
+ **Cause**: corrupted or partial install of the `tkeron` package.
64
+
65
+ Fix: `bun add -g tkeron@latest` (or reinstall locally).
66
+
67
+ ### Build hangs forever
68
+
69
+ **Cause**: `fetch()` without timeout in `.pre.ts` / `.post.ts` / `.com.ts`. The build event loop waits for the promise.
70
+
71
+ Fix: always wrap external fetches with `AbortController` + a 5 s timeout. See pattern in `tkeron-patterns`.
72
+
73
+ ### `Maximum iterations reached` (or component loop never settles)
74
+
75
+ **Cause**: a `.com.ts` keeps emitting the same custom element it is processing, producing infinite expansion. Or two components reference each other circularly.
76
+
77
+ Fix:
78
+
79
+ 1. Find the component whose `innerHTML` contains its own tag.
80
+ 2. Break the cycle (emit raw HTML instead of the wrapper, or use a different tag).
81
+ 3. Limit: 10 iterations, 50 nesting levels — exceeding either aborts the build.
82
+
83
+ ---
84
+
85
+ ## Output Looks Wrong
86
+
87
+ ### A component tag is still in `web/index.html`
88
+
89
+ ```html
90
+ <!-- Final output still shows: -->
91
+ <user-card></user-card>
92
+ ```
93
+
94
+ Causes (in order of frequency):
95
+
96
+ 1. **File name mismatch**: tag is `<user-card>` but file is `userCard.com.html` or `usercard.com.html`. Tkeron is case-insensitive but expects matching kebab-case.
97
+ 2. **Wrong extension**: file ends in `.html` instead of `.com.html`.
98
+ 3. **File outside `websrc/`**: tkeron only scans `websrc/`.
99
+ 4. **Typo**: `<user-cards>` vs `user-card.com.html`.
100
+
101
+ Verify:
102
+
103
+ ```bash
104
+ ls websrc/**/*.com.* | grep -i user-card
105
+ grep -rn "<user-card" websrc/
106
+ ```
107
+
108
+ ### A `.com.ts` runs but `com.innerHTML` is empty in output
109
+
110
+ **Cause**: the `.com.ts` did not assign `com.innerHTML`. Only `com.innerHTML` survives — `textContent`, `appendChild`, `setAttribute` on `com` itself are all discarded.
111
+
112
+ ```typescript
113
+ // ❌ — discarded
114
+ com.textContent = "hi";
115
+ com.appendChild(document.createElement("span"));
116
+
117
+ // ✅ — preserved
118
+ com.innerHTML = `<span>hi</span>`;
119
+ ```
120
+
121
+ ### A `<script>` inside `com.innerHTML` is corrupted in output
122
+
123
+ **Cause**: the HTML parser re-serializes `innerHTML`, and `<`, `>`, `&` inside script JS get double-escaped.
124
+
125
+ Fix:
126
+
127
+ - Avoid comparison operators inside the inline script (`>=`, `<=`, `>`, `<`).
128
+ - Use `matchMedia('(min-width: Xpx)')` instead of `window.innerWidth >= X`.
129
+ - Move complex logic to a separate browser `.ts` file and only emit a `<script type="module" src="./x.ts"></script>` tag.
130
+
131
+ ### Custom CSS selector targets the wrapper tag and matches nothing
132
+
133
+ **Cause**: the wrapper tag (`<my-section>`) disappears from the output — only the children of `innerHTML` remain.
134
+
135
+ Fix: write CSS targeting the class/element you emit **inside** `innerHTML`, never the custom tag. See `tkeron-components` → "wrapper tag disappears".
136
+
137
+ ### `.pre.ts` ran but the page does not reflect it
138
+
139
+ Checklist:
140
+
141
+ 1. Is the file named EXACTLY `<page>.pre.ts` next to `<page>.html`? Pairing is by file name.
142
+ 2. Did the script throw silently? Run `tk build` and read the full stderr.
143
+ 3. Are you querying nodes that don't exist yet because the component loop hasn't run? `.pre.ts` runs BEFORE components — query only what is in the source HTML.
144
+
145
+ ### `.post.ts` ran but did not see component output
146
+
147
+ Same as above, but reversed: `.post.ts` runs AFTER the component loop, so you CAN query nodes emitted by components. If you can't, check the script name pairs the page, and that you didn't accidentally use `.pre.ts` instead.
148
+
149
+ ### Assets disappeared from `web/`
150
+
151
+ Check:
152
+
153
+ 1. File names contain spaces? Tkeron silently fails to copy them. Rename to kebab-case.
154
+ 2. File is inside a `.com.*` or `.pre.ts` pair? Those are excluded from output by design.
155
+ 3. File has the `.com.html` / `.com.ts` / `.com.md` / `.pre.ts` / `.post.ts` extension? Those are never copied.
156
+
157
+ ---
158
+
159
+ ## Dev Server Recovery
160
+
161
+ `tk dev` is a **singleton process** per port. The first thing to check is whether it is even running.
162
+
163
+ ### Check status
164
+
165
+ ```bash
166
+ fuser 3000/tcp 2>/dev/null
167
+ # Empty output → not running
168
+ # Has output → running
169
+ ```
170
+
171
+ ### Recovery after a crash
172
+
173
+ `tk dev` can crash when an edit in `websrc/` introduces a compile or syntax error. Steps:
174
+
175
+ 1. Confirm it's down: `fuser 3000/tcp 2>/dev/null` returns empty.
176
+ 2. Read the terminal where `tk dev` was running — the last lines explain why it crashed.
177
+ 3. Identify the offending file in `websrc/` (TS error, malformed component, broken import).
178
+ 4. Fix the file.
179
+ 5. Relaunch **in background**:
180
+ ```bash
181
+ tk dev
182
+ ```
183
+ (In agent sessions, this MUST be an async terminal — `tk dev` blocks indefinitely.)
184
+ 6. Confirm it is up: `fuser 3000/tcp 2>/dev/null` returns output.
185
+
186
+ ### Port already in use
187
+
188
+ ```
189
+ Error: listen EADDRINUSE :::3000
190
+ ```
191
+
192
+ Cause: another `tk dev` (or another process) holds port 3000.
193
+
194
+ ```bash
195
+ fuser -k 3000/tcp # kill the holder
196
+ # OR launch on a different port:
197
+ tk dev 3001
198
+ ```
199
+
200
+ ### Hot reload not firing
201
+
202
+ 1. Browser tab too old (before the reload script was injected) → hard refresh once (`Ctrl+Shift+R`).
203
+ 2. SSE connection blocked by a proxy/extension → check DevTools → Network → `/dev-reload` should be `text/event-stream`, status `200`, hanging open.
204
+ 3. The watcher missed the file (rare on network volumes / Docker bind mounts) → restart `tk dev`.
205
+
206
+ ### `tk dev` blocks the agent terminal
207
+
208
+ `tk dev` runs forever by design. In agent sessions:
209
+
210
+ - ALWAYS launch it as an async terminal (background mode).
211
+ - NEVER run it synchronously — it will time out and leave a zombie.
212
+ - Use `fuser 3000/tcp 2>/dev/null` as the readiness check, not a sleep.
213
+
214
+ ---
215
+
216
+ ## Debugging Workflow
217
+
218
+ When something is wrong, follow this order:
219
+
220
+ 1. **Reproduce with `tk build`** (not `tk dev`) — it's deterministic and gives full stderr.
221
+ 2. **Read `web/<page>.html`** — does the component appear unprocessed? Are scripts referenced as `.js`?
222
+ 3. **Isolate**: comment out custom elements one by one to find the offender.
223
+ 4. **Strip to minimum**: replace the suspect `.com.ts` body with `com.innerHTML = "OK";` and rebuild — confirms whether the file is even loaded.
224
+ 5. **Check naming**: file name vs tag name (kebab-case, hyphen, no collisions with standard HTML).
225
+ 6. **Check pairing**: `.pre.ts` / `.post.ts` must share the base name with the `.html`.
226
+ 7. **Check imports**: relative paths, no `.js` extensions for source TS files imported from build-time scripts.
227
+
228
+ ---
229
+
230
+ ## IDE vs Build
231
+
232
+ The IDE can show errors that the build does not (and vice versa):
233
+
234
+ | Symptom | What it means |
235
+ | --------------------------------------- | ---------------------------------------------------------------- |
236
+ | IDE: red squiggle on `com` | `tkeron.d.ts` missing or not in `tsconfig.json` `include`. |
237
+ | IDE: red squiggle on `document` in .pre | Same as above for the `*.pre.ts` declaration. |
238
+ | Build: passes but IDE shows type errors | Tkeron does NOT type-check. Run `bun x tsc --noEmit` to confirm. |
239
+ | Build: fails with a TS-looking error | It's a Bun **compile** error, not a type error — fix the syntax. |
240
+
241
+ Recommended pre-commit / CI step:
242
+
243
+ ```bash
244
+ bun x tsc --noEmit && tk build
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Last-Resort Reset
250
+
251
+ If the project is in a weird state (stale temp dirs, mismatched output):
252
+
253
+ ```bash
254
+ # Remove the output and any leftover temp build dirs
255
+ rm -rf web/ .tktmp_*
256
+ tk build
257
+ ```
258
+
259
+ `getBuildResult` cleans its own temp dirs (`.tktmp_buildresult-*`), but a hard-killed build can leave them behind. They are always siblings of `websrc/`.
package/src/build.ts CHANGED
@@ -8,11 +8,9 @@ import { processPost } from "./processPost";
8
8
  import { deduplicateComStyles } from "./deduplicateComStyles";
9
9
  import { rm, exists, mkdir, cp } from "fs/promises";
10
10
  import type { Logger } from "@tkeron/tools";
11
- import { silentLogger } from "@tkeron/tools";
12
- import {
13
- cleanupOrphanedTempDirs,
14
- TEMP_DIR_PREFIX,
15
- } from "./cleanupOrphanedTempDirs";
11
+ import { silentLogger, cleanupOrphanedTempDirs } from "@tkeron/tools";
12
+
13
+ export const TEMP_DIR_PREFIX = ".tktmp_build-";
16
14
 
17
15
  export interface BuildOptions {
18
16
  sourceDir?: string;
@@ -38,7 +36,7 @@ export const build = async (options: BuildOptions) => {
38
36
  `${TEMP_DIR_PREFIX}${crypto.randomUUID()}`,
39
37
  );
40
38
 
41
- await cleanupOrphanedTempDirs(sourceParent, log);
39
+ await cleanupOrphanedTempDirs(sourceParent, TEMP_DIR_PREFIX, log);
42
40
 
43
41
  try {
44
42
  await mkdir(tempDir, { recursive: true });
package/src/develop.ts CHANGED
@@ -2,8 +2,7 @@ import { resolve, dirname } from "path";
2
2
  import { watch } from "fs";
3
3
  import { build } from "./build";
4
4
  import type { Logger } from "@tkeron/tools";
5
- import { logger as defaultLogger } from "@tkeron/tools";
6
- import { setupSigintHandler } from "./setupSigintHandler";
5
+ import { logger as defaultLogger, setupSigintHandler } from "@tkeron/tools";
7
6
 
8
7
  export interface TkeronDevOptions {
9
8
  outputDir?: string;
package/src/init.ts CHANGED
@@ -9,8 +9,7 @@ import {
9
9
  } from "fs";
10
10
  import { join, resolve, basename } from "path";
11
11
  import type { Logger } from "@tkeron/tools";
12
- import { silentLogger } from "@tkeron/tools";
13
- import { promptUser } from "./promptUser";
12
+ import { silentLogger, promptUser } from "@tkeron/tools";
14
13
  import { ensureTsconfig } from "./ensureTsconfig";
15
14
 
16
15
  export interface InitOptions {
package/src/skills.ts ADDED
@@ -0,0 +1,139 @@
1
+ import {
2
+ existsSync,
3
+ readdirSync,
4
+ mkdirSync,
5
+ cpSync,
6
+ statSync,
7
+ readFileSync,
8
+ writeFileSync,
9
+ } from "fs";
10
+ import { join, resolve } from "path";
11
+ import type { Logger } from "@tkeron/tools";
12
+ import {
13
+ silentLogger,
14
+ detectEnvironments,
15
+ addFrontmatterField,
16
+ } from "@tkeron/tools";
17
+ import type { Environment } from "@tkeron/tools";
18
+
19
+ export interface SkillsOptions {
20
+ target?: string;
21
+ force?: boolean;
22
+ dryRun?: boolean;
23
+ logger?: Logger;
24
+ }
25
+
26
+ const getTransform = (env: Environment) => {
27
+ if (env === "cursor") {
28
+ return (content: string) =>
29
+ addFrontmatterField(content, "alwaysApply", false);
30
+ }
31
+ return null;
32
+ };
33
+
34
+ const installToDir = (
35
+ skillNames: string[],
36
+ sourceDir: string,
37
+ targetDir: string,
38
+ transform: ((content: string) => string) | null,
39
+ force: boolean,
40
+ log: Logger,
41
+ ) => {
42
+ const conflicts: string[] = [];
43
+ for (const name of skillNames) {
44
+ const dest = join(targetDir, name, "SKILL.md");
45
+ if (existsSync(dest)) {
46
+ conflicts.push(name);
47
+ }
48
+ }
49
+
50
+ if (conflicts.length > 0 && !force) {
51
+ throw new Error(
52
+ `Skill files already exist: ${conflicts.join(", ")}. Use --force to overwrite.`,
53
+ );
54
+ }
55
+
56
+ mkdirSync(targetDir, { recursive: true });
57
+
58
+ for (const name of skillNames) {
59
+ const src = join(sourceDir, name);
60
+ const dest = join(targetDir, name);
61
+ cpSync(src, dest, { recursive: true, force: true });
62
+
63
+ if (transform) {
64
+ const skillFile = join(dest, "SKILL.md");
65
+ if (existsSync(skillFile)) {
66
+ const content = readFileSync(skillFile, "utf-8");
67
+ writeFileSync(skillFile, transform(content), "utf-8");
68
+ }
69
+ }
70
+
71
+ log.log(`✓ Installed skill: ${name}`);
72
+ }
73
+ };
74
+
75
+ const previewToDir = (skillNames: string[], targetDir: string, log: Logger) => {
76
+ for (const name of skillNames) {
77
+ const dest = join(targetDir, name, "SKILL.md");
78
+ log.log(`○ Would install: ${name} → ${dest}`);
79
+ }
80
+ };
81
+
82
+ export const skills = async (options: SkillsOptions = {}) => {
83
+ const {
84
+ target,
85
+ force = false,
86
+ dryRun = false,
87
+ logger: log = silentLogger,
88
+ } = options;
89
+
90
+ const sourceDir = join(import.meta.dir, "..", "skills");
91
+
92
+ if (!existsSync(sourceDir)) {
93
+ throw new Error("Skills directory not found in tkeron installation");
94
+ }
95
+
96
+ const skillNames = readdirSync(sourceDir).filter((name) =>
97
+ statSync(join(sourceDir, name)).isDirectory(),
98
+ );
99
+
100
+ if (skillNames.length === 0) {
101
+ throw new Error("No skills available to install");
102
+ }
103
+
104
+ if (target !== undefined) {
105
+ const targetDir = resolve(process.cwd(), target);
106
+ if (dryRun) {
107
+ previewToDir(skillNames, targetDir, log);
108
+ log.log(
109
+ `\n📋 ${skillNames.length} skill(s) would be installed in ${targetDir}`,
110
+ );
111
+ } else {
112
+ installToDir(skillNames, sourceDir, targetDir, null, force, log);
113
+ log.log(`\n✅ ${skillNames.length} skill(s) installed in ${targetDir}`);
114
+ }
115
+ return;
116
+ }
117
+
118
+ const detected = detectEnvironments(process.cwd());
119
+
120
+ if (detected.length === 0) {
121
+ throw new Error("NO_ENVIRONMENTS_DETECTED");
122
+ }
123
+
124
+ for (const { env, target: envTarget } of detected) {
125
+ const targetDir = resolve(process.cwd(), envTarget);
126
+ if (dryRun) {
127
+ previewToDir(skillNames, targetDir, log);
128
+ log.log(
129
+ `\n📋 ${skillNames.length} skill(s) would be installed for ${env} in ${targetDir}`,
130
+ );
131
+ } else {
132
+ const transform = getTransform(env);
133
+ installToDir(skillNames, sourceDir, targetDir, transform, force, log);
134
+ log.log(
135
+ `\n✅ ${skillNames.length} skill(s) installed for ${env} in ${targetDir}`,
136
+ );
137
+ }
138
+ }
139
+ };
@@ -0,0 +1,79 @@
1
+ import { skills } from "./skills.js";
2
+ import type { Logger } from "@tkeron/tools";
3
+ import { logger as defaultLogger } from "@tkeron/tools";
4
+ import { createInterface } from "readline";
5
+
6
+ const stdinConfirm = async (message: string): Promise<boolean> => {
7
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
8
+ return new Promise<boolean>((resolve) => {
9
+ rl.question(message, (answer) => {
10
+ rl.close();
11
+ const trimmed = answer.trim().toLowerCase();
12
+ resolve(trimmed === "y" || trimmed === "yes");
13
+ });
14
+ });
15
+ };
16
+
17
+ export interface SkillsWrapperOptions {
18
+ target?: string;
19
+ force?: boolean;
20
+ dryRun?: boolean;
21
+ logger?: Logger;
22
+ confirm?: (message: string) => Promise<boolean>;
23
+ [key: string]: any;
24
+ }
25
+
26
+ export const skillsWrapper = async (options: SkillsWrapperOptions = {}) => {
27
+ const log = options?.logger || defaultLogger;
28
+ const confirm = options?.confirm ?? stdinConfirm;
29
+
30
+ try {
31
+ await skills({
32
+ target: options.target,
33
+ force: options.force,
34
+ dryRun: options.dryRun,
35
+ logger: log,
36
+ });
37
+ } catch (error: any) {
38
+ if (error.message === "NO_ENVIRONMENTS_DETECTED") {
39
+ if (options.dryRun) {
40
+ log.log(
41
+ "\n⚠ No known AI environment detected (.cursor/, .github/, .claude/).",
42
+ );
43
+ log.log(" Would create a skills/ directory in the current directory.");
44
+ return;
45
+ }
46
+ log.log(
47
+ "\n⚠ No known AI environment detected (.cursor/, .github/, .claude/).",
48
+ );
49
+ log.log(
50
+ " A skills/ directory will be created in the current directory instead.",
51
+ );
52
+
53
+ const proceed = await confirm(" Continue? (y/N): ");
54
+ if (!proceed) {
55
+ log.log("Aborted.");
56
+ return;
57
+ }
58
+
59
+ try {
60
+ await skills({ target: "skills", force: options.force, logger: log });
61
+ } catch (err: any) {
62
+ log.error(`\n❌ ${err.message}\n`);
63
+ if (/already exist/i.test(err.message)) {
64
+ log.error(
65
+ "💡 Tip: pass --force to overwrite existing skill files.\n",
66
+ );
67
+ }
68
+ process.exit(1);
69
+ }
70
+ return;
71
+ }
72
+
73
+ log.error(`\n❌ ${error.message}\n`);
74
+ if (/already exist/i.test(error.message)) {
75
+ log.error("💡 Tip: pass --force to overwrite existing skill files.\n");
76
+ }
77
+ process.exit(1);
78
+ }
79
+ };