@techninja/clearstack 0.4.5 → 0.4.6

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.
@@ -96,6 +96,12 @@ export function buildCmds(projectDir, opts) {
96
96
  prettier: `${runner} prettier --config .configs/.prettierrc --check src scripts`,
97
97
  mdlint: `${runner} markdownlint-cli2 --config .configs/.markdownlint.jsonc "docs/**/*.md" "*.md"`,
98
98
  audit,
99
+ fix: {
100
+ es: `${runner} eslint --config .configs/eslint.config.js . --fix`,
101
+ css: `${runner} stylelint --config .configs/.stylelintrc.json "src/**/*.css" --fix`,
102
+ prettier: `${runner} prettier --config .configs/.prettierrc --write src scripts`,
103
+ md: `${runner} markdownlint-cli2 --config .configs/.markdownlint.jsonc --fix "docs/**/*.md" "*.md"`,
104
+ },
99
105
  };
100
106
  return {
101
107
  lint: `${runner} eslint --config .configs/eslint.config.js . --fix`,
package/lib/watch-ui.js CHANGED
@@ -64,7 +64,11 @@ export function extractViolations(row, projectDir) {
64
64
  });
65
65
  }
66
66
  if (row.result?.errors?.length) {
67
- return [[(row.label ?? row.name ?? row.key), row.result.errors.slice(0, 3).join('\n')]];
67
+ const errors = row.result.errors;
68
+ // Prettier: [warn] src/file.js — expand each into its own entry
69
+ const prettierFiles = errors.map((l) => l.match(/^\[warn\]\s+(.+\.\w+)$/)?.[1]).filter(Boolean);
70
+ if (prettierFiles.length) return prettierFiles.map((f) => [f, 'formatting — run npm run format']);
71
+ return [[row.label ?? row.name ?? row.key, errors.slice(0, 5).join('\n')]];
68
72
  }
69
73
  return [];
70
74
  }
@@ -84,6 +88,9 @@ function rowIcon(r) {
84
88
  return '{red-fg}!{/} ';
85
89
  }
86
90
 
91
+ /** @param {object} r @param {number} w */
92
+ const rowLine = (r, w) => { const n = (r.label ?? r.name ?? r.key).padEnd(w); return ` ${rowIcon(r)} ${r.pass === false ? `{red-fg}${n}{/}` : n} ${r.detail ? `{grey-fg}${r.detail}{/}` : ''}`; };
93
+
87
94
  /**
88
95
  * Render the dashboard.
89
96
  * @param {object[]} rows
@@ -92,13 +99,8 @@ function rowIcon(r) {
92
99
  * @param {string[][]} violations
93
100
  */
94
101
  export function render(rows, lastCheck, watchDirs, violations) {
95
- const labelWidth = Math.max(...rows.map((r) => (r.label ?? r.name ?? r.key).length));
96
- const statusLines = rows.map((r) => {
97
- const name = (r.label ?? r.name ?? r.key).padEnd(labelWidth);
98
- const styled = r.pass === false ? `{red-fg}${name}{/}` : name;
99
- const detail = r.detail ? `{grey-fg}${r.detail}{/}` : '';
100
- return ` ${rowIcon(r)} ${styled} ${detail}`;
101
- });
102
+ const w = Math.max(...rows.map((r) => (r.label ?? r.name ?? r.key).length));
103
+ const statusLines = rows.map((r) => rowLine(r, w));
102
104
  statusLines.push('');
103
105
  statusLines.push(`{grey-fg} watching ${watchDirs.join(', ')} last check: ${lastCheck}{/}${getCopyNote()}`);
104
106
  statusBox.setContent(statusLines.join('\n'));
@@ -137,12 +139,7 @@ export function destroyScreen() {
137
139
 
138
140
  /** Refresh just the status box — used for transient notes like copy confirmation. */
139
141
  export function renderNote(rows, lastCheck, watchDirs) {
140
- const labelWidth = Math.max(...rows.map((r) => (r.label ?? r.name ?? r.key).length));
141
- const lines = [
142
- ...rows.map((r) => { const n = (r.label ?? r.name ?? r.key).padEnd(labelWidth); return ` ${rowIcon(r)} ${r.pass === false ? `{red-fg}${n}{/}` : n} ${r.detail ? `{grey-fg}${r.detail}{/}` : ''}`; }),
143
- '',
144
- `{grey-fg} watching ${watchDirs.join(', ')} last check: ${lastCheck}{/}${getCopyNote()}`,
145
- ];
146
- statusBox.setContent(lines.join('\n'));
142
+ const w = Math.max(...rows.map((r) => (r.label ?? r.name ?? r.key).length));
143
+ statusBox.setContent([...rows.map((r) => rowLine(r, w)), '', `{grey-fg} watching ${watchDirs.join(', ')} last check: ${lastCheck}{/}${getCopyNote()}`].join('\n'));
147
144
  screen.render();
148
145
  }
@@ -15,7 +15,7 @@ export const screen = blessed.screen({ smartCSR: true, title: 'Clearstack Spec W
15
15
  export const outer = blessed.box({
16
16
  top: 0, left: 0, width: '100%', height: '100%',
17
17
  border: { type: 'line' },
18
- label: { text: ' {blue-fg}♥{/blue-fg} Clearstack Spec Watch q quit ↑↓ scroll c copy {/}', side: 'left' },
18
+ label: { text: ' {blue-fg}♥{/blue-fg} Clearstack Spec Watch q quit ↑↓ scroll c copy f fix {/}', side: 'left' },
19
19
  tags: true,
20
20
  style: { border: { fg: 'cyan' }, label: { fg: 'grey', bold: false } },
21
21
  });
@@ -58,9 +58,13 @@ export const getCopyNote = () => _copyNote;
58
58
  let _onCopyNote = () => {};
59
59
  export const setOnCopyNote = (fn) => { _onCopyNote = fn; };
60
60
 
61
+ let _onFix = () => {};
62
+ export const setOnFix = (fn) => { _onFix = fn; };
63
+
61
64
  screen.key(['C-c', 'q'], () => _quit());
62
65
  screen.key(['pageup', 'up'], () => { logBox.scroll(-1); screen.render(); });
63
66
  screen.key(['pagedown', 'down'], () => { logBox.scroll(1); screen.render(); });
67
+ screen.key(['f'], () => _onFix());
64
68
  screen.key(['c'], () => {
65
69
  const raw = logBox.getContent()
66
70
  .replace(/\{[^}]+\}/g, '') // blessed tags
package/lib/watch.js CHANGED
@@ -7,11 +7,13 @@
7
7
 
8
8
  import { existsSync } from 'node:fs';
9
9
  import { resolve } from 'node:path';
10
+ import { execSync } from 'node:child_process';
10
11
  import { loadConfig, buildCmds, makeExtMap, FAST_CHECKS } from './spec-config.js';
11
12
  import { buildChecks } from './check.js';
12
13
  import { render, extractViolations, renderNote } from './watch-ui.js';
13
14
  import { spawnServer, detectServerCmd } from './server-proc.js';
14
15
  import { setupLifecycle } from './watch-lifecycle.js';
16
+ import { setOnFix } from './watch-widgets.js';
15
17
 
16
18
  /** Adapt a Check from buildChecks into a watch row (adds pass/detail/result state). */
17
19
  function toRow(check) {
@@ -123,6 +125,18 @@ export async function startWatch(projectDir) {
123
125
 
124
126
  runKeys(new Set(specRows.map((r) => r.key)));
125
127
 
128
+ // f key — run fix commands for all currently-failing fixable checks
129
+ setOnFix(() => {
130
+ const fixable = specRows.filter((r) => !r.pass && cmds.fix?.[r.key]);
131
+ if (!fixable.length) return;
132
+ for (const row of fixable) {
133
+ try { execSync(cmds.fix[row.key], { cwd: projectDir, stdio: 'pipe' }); } catch { /* linter may exit 1 even after fixing */ }
134
+ slowPending.add(row.key);
135
+ }
136
+ clearTimeout(slowTimer);
137
+ slowTimer = setTimeout(runSlow, 100);
138
+ });
139
+
126
140
  setupLifecycle({
127
141
  serverRow, specRows, projectDir, watchDirs,
128
142
  schedule,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techninja/clearstack",
3
- "version": "0.4.5",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "description": "A no-build web component framework specification — scaffold, validate, and evolve spec-compliant projects",
6
6
  "bin": {