nothumanallowed 16.0.43 → 16.0.45

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": "nothumanallowed",
3
- "version": "16.0.43",
3
+ "version": "16.0.45",
4
4
  "description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/constants.mjs CHANGED
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
5
5
  const __filename = fileURLToPath(import.meta.url);
6
6
  const __dirname = path.dirname(__filename);
7
7
 
8
- export const VERSION = '16.0.43';
8
+ export const VERSION = '16.0.45';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -229,6 +229,21 @@ class SandboxManager {
229
229
  }
230
230
  } catch {}
231
231
 
232
+ // Pre-flight: auto-extend CSS if coverage is below threshold.
233
+ // Detects "incomplete styling" (LLM truncated CSS generation) and calls
234
+ // the LLM automatically to extend the CSS file with rules for the missing
235
+ // HTML selectors. NO user intervention required.
236
+ try {
237
+ const projectName = path.basename(projectDir);
238
+ const cfg = loadConfig();
239
+ const styleResult = await _autoExtendStylesIfNeeded(projectName, cfg, emit, { minCoverage: 0.6 });
240
+ if (styleResult.extended) {
241
+ emit({ type: 'status', msg: `Auto-extended styles: ${styleResult.file} (${styleResult.missingBefore - styleResult.missingAfter} new selectors covered).` });
242
+ }
243
+ } catch (e) {
244
+ emit({ type: 'warn', msg: `Auto-extend styles failed: ${(e.message || e).slice(0, 200)}` });
245
+ }
246
+
232
247
  // Pre-flight: complete missing CSS/JS assets via sibling fill + LLM gen.
233
248
  // Replaces the previous "empty placeholder" strategy with real content
234
249
  // when available. LLM calls capped at 8 files per boot to keep latency
@@ -3460,6 +3475,17 @@ ${original.slice(0, 12_000)}`;
3460
3475
  } catch (e) { console.error('[scan] CRASH:', e); sendError(res, 500, e.message); }
3461
3476
  });
3462
3477
 
3478
+ // ── Auto-extend CSS when coverage is below threshold ──────────────────────
3479
+ router.post('/api/studio/webcraft/extend-styles', async (req, res) => {
3480
+ try {
3481
+ const { projectName, minCoverage } = await parseBody(req);
3482
+ if (!projectName) return sendError(res, 400, 'projectName required');
3483
+ const config = loadConfig();
3484
+ const result = await _autoExtendStylesIfNeeded(projectName, config, null, { minCoverage: minCoverage ?? 0.6 });
3485
+ sendJSON(res, 200, result);
3486
+ } catch (e) { sendError(res, 500, e.message); }
3487
+ });
3488
+
3463
3489
  // ── Smart asset completion (sibling fill + LLM generation) ────────────────
3464
3490
  // For missing CSS/JS referenced in HTML: first try copying from sibling
3465
3491
  // files with real content (deterministic, instant), then LLM-generate
@@ -3787,6 +3813,25 @@ export function _detectMissingDataFiles(projectDir) {
3787
3813
  return created;
3788
3814
  }
3789
3815
 
3816
+ // Names that imply specific functionality — sibling fill is forbidden because
3817
+ // the file MUST have specific content, not a generic copy of style.css.
3818
+ // LLM generation is preferred for these.
3819
+ const _SEMANTIC_FILE_NAMES = new Set([
3820
+ 'animations', 'animation', 'transitions', 'transition',
3821
+ 'theme', 'themes', 'dark', 'light',
3822
+ 'charts', 'chart', 'graphs', 'graph', 'plot',
3823
+ 'auth', 'authentication', 'login', 'signup', 'register',
3824
+ 'dashboard', 'admin', 'profile',
3825
+ 'portfolio', 'gallery', 'projects',
3826
+ 'reset', 'normalize', 'print',
3827
+ 'mobile', 'responsive', 'desktop',
3828
+ ]);
3829
+
3830
+ function _hasSemanticName(filePath) {
3831
+ const base = path.basename(filePath).replace(/\.[^.]+$/, '').toLowerCase();
3832
+ return _SEMANTIC_FILE_NAMES.has(base);
3833
+ }
3834
+
3790
3835
  /**
3791
3836
  * Score how similar two filenames are. Returns 0..1.
3792
3837
  * "main.css" vs "style.css" → some score based on prefix/suffix shared chars.
@@ -3813,17 +3858,26 @@ function _fileSimilarity(a, b) {
3813
3858
  * E.g. missing `public/css/main.css` but `public/css/style.css` exists with
3814
3859
  * 8459 bytes — that's almost certainly what the LLM meant.
3815
3860
  */
3816
- function _findSiblingFile(projectDir, missingRel) {
3861
+ function _findSiblingFile(projectDir, missingRel, exclude) {
3862
+ // Refuse sibling fill for semantic file names — they need real content
3863
+ // matching the name's intent, not a copy of style.css.
3864
+ if (_hasSemanticName(missingRel)) return null;
3817
3865
  const missingAbs = path.join(projectDir, missingRel);
3818
3866
  const dir = path.dirname(missingAbs);
3819
3867
  const ext = path.extname(missingRel).toLowerCase();
3820
3868
  if (!fs.existsSync(dir)) return null;
3821
3869
  let entries;
3822
3870
  try { entries = fs.readdirSync(dir); } catch { return null; }
3871
+ const excludeSet = exclude instanceof Set ? exclude : new Set();
3823
3872
  const candidates = [];
3824
3873
  for (const name of entries) {
3825
3874
  if (path.extname(name).toLowerCase() !== ext) continue;
3875
+ if (name === path.basename(missingRel)) continue;
3826
3876
  const abs = path.join(dir, name);
3877
+ // Skip files we just filled in this session (avoid filling A from B
3878
+ // when B was itself just filled from C — produces semantic duplicates)
3879
+ const relFromProject = path.relative(projectDir, abs).replace(/\\/g, '/');
3880
+ if (excludeSet.has(relFromProject)) continue;
3827
3881
  let stat;
3828
3882
  try { stat = fs.statSync(abs); } catch { continue; }
3829
3883
  if (!stat.isFile() || stat.size < 200) continue;
@@ -3831,6 +3885,8 @@ function _findSiblingFile(projectDir, missingRel) {
3831
3885
  try {
3832
3886
  const head = fs.readFileSync(abs, 'utf-8').slice(0, 200);
3833
3887
  if (/nha-webcraft:.*auto-created placeholder/i.test(head)) continue;
3888
+ // Skip files with semantic names — they have specific purpose
3889
+ if (_hasSemanticName(name)) continue;
3834
3890
  } catch { continue; }
3835
3891
  const sim = _fileSimilarity(path.basename(missingRel), name);
3836
3892
  candidates.push({ name, abs, size: stat.size, similarity: sim });
@@ -3940,23 +3996,51 @@ export async function _completeMissingAssets(projectName, config, emit) {
3940
3996
 
3941
3997
  if (emit) emit({ type: 'status', msg: 'Completing ' + missingByPath.size + ' missing/placeholder asset' + (missingByPath.size === 1 ? '' : 's') + '...' });
3942
3998
 
3943
- // Phase 1: sibling fill (deterministic, instant)
3999
+ // Phase 1: HTML reference rewrite (NO file duplication).
4000
+ // When `main.css` is referenced but missing AND `style.css` exists in the
4001
+ // same dir with real content, the correct fix is NOT to copy style.css
4002
+ // into main.css (creates a duplicate with mismatched semantic name) but
4003
+ // to REWRITE the HTML <link href="main.css"> → <link href="style.css">.
4004
+ // This preserves the LLM's actual file structure.
4005
+ report.htmlRewrites = report.htmlRewrites || [];
4006
+ const referencedFromMap = new Map(); // htmlPath → list of {oldHref, newHref}
3944
4007
  const stillMissing = [];
3945
4008
  for (const [target, info] of missingByPath) {
3946
- const sibling = _findSiblingFile(dir, target);
4009
+ const sibling = _findSiblingFile(dir, target, new Set());
3947
4010
  if (sibling) {
3948
- try {
3949
- const targetAbs = path.join(dir, target);
3950
- ensureDir(path.dirname(targetAbs));
3951
- fs.copyFileSync(sibling.abs, targetAbs);
3952
- report.siblingFills.push({ target, source: sibling.name, size: sibling.size });
3953
- if (emit) emit({ type: 'status', msg: 'Sibling-filled: ' + target + ' ' + sibling.name + ' (' + sibling.size + ' bytes)' });
3954
- continue;
3955
- } catch {}
4011
+ // Don't copy. Rewrite the HTML reference to point to the real file.
4012
+ const newRef = path.posix.relative(
4013
+ path.dirname(info.referencedFrom),
4014
+ path.relative(dir, sibling.abs).replace(/\\/g, '/')
4015
+ ) || path.basename(sibling.name);
4016
+ const htmlRewrite = { from: info.referencedFrom, oldHref: target, newHref: newRef, sibling: sibling.name };
4017
+ if (!referencedFromMap.has(info.referencedFrom)) referencedFromMap.set(info.referencedFrom, []);
4018
+ referencedFromMap.get(info.referencedFrom).push(htmlRewrite);
4019
+ report.htmlRewrites.push(htmlRewrite);
4020
+ if (emit) emit({ type: 'status', msg: `HTML rewrite: ${info.referencedFrom} → ${target} now points to ${sibling.name} (${sibling.size} bytes, real content)` });
4021
+ continue;
3956
4022
  }
3957
4023
  stillMissing.push({ target, ...info });
3958
4024
  }
3959
4025
 
4026
+ // Apply HTML rewrites (one pass per file)
4027
+ for (const [htmlRel, rewrites] of referencedFromMap) {
4028
+ try {
4029
+ const htmlAbs = path.join(dir, htmlRel);
4030
+ let html = fs.readFileSync(htmlAbs, 'utf-8');
4031
+ for (const r of rewrites) {
4032
+ // Rewrite both `href="X"` and `src="X"` for the old asset path.
4033
+ // Handle both relative ('css/main.css') and absolute ('/css/main.css').
4034
+ const oldBase = path.basename(r.oldHref);
4035
+ const re = new RegExp(`(href|src)=(["'])([^"']*${oldBase.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})\\2`, 'g');
4036
+ html = html.replace(re, (_m, attr, q) => `${attr}=${q}${r.newHref}${q}`);
4037
+ }
4038
+ fs.writeFileSync(htmlAbs, html, 'utf-8');
4039
+ } catch (e) {
4040
+ if (emit) emit({ type: 'warn', msg: `Failed to rewrite ${htmlRel}: ${e.message}` });
4041
+ }
4042
+ }
4043
+
3960
4044
  // Phase 2: LLM completion for remaining (one call per file, max 8 files)
3961
4045
  const maxLLM = 8;
3962
4046
  for (const m of stillMissing.slice(0, maxLLM)) {
@@ -4000,6 +4084,229 @@ export async function _completeMissingAssets(projectName, config, emit) {
4000
4084
  return report;
4001
4085
  }
4002
4086
 
4087
+ /**
4088
+ * Analyze CSS coverage for an HTML project. Returns the percentage of HTML
4089
+ * classes/ids that have at least one matching CSS rule.
4090
+ *
4091
+ * Returns {
4092
+ * coverage: 0..1,
4093
+ * htmlSelectors: ['.cta', '.hero', '#main', ...], // all unique selectors in HTML
4094
+ * cssSelectors: Set of selectors that have CSS rules
4095
+ * missing: ['.testimonial', '.pricing-card', ...] // HTML selectors with no CSS
4096
+ * tagCount: number of HTML elements found
4097
+ * cssRuleCount: number of rules in all CSS files
4098
+ * imgCount: number of <img> tags
4099
+ * hasImgRule: boolean — does any CSS rule target `img` with max-width?
4100
+ * }
4101
+ */
4102
+ export function _analyzeCssCoverage(projectDir) {
4103
+ const result = {
4104
+ coverage: 1,
4105
+ htmlSelectors: [],
4106
+ cssSelectors: new Set(),
4107
+ missing: [],
4108
+ tagCount: 0,
4109
+ cssRuleCount: 0,
4110
+ imgCount: 0,
4111
+ hasImgRule: false,
4112
+ cssFiles: [],
4113
+ htmlFiles: [],
4114
+ };
4115
+
4116
+ // Gather all HTML and CSS files
4117
+ const stack = [projectDir];
4118
+ const skipDirs = new Set(['node_modules', '.git', '.nha-shims', 'dist', 'build', '.next']);
4119
+ while (stack.length) {
4120
+ const cur = stack.pop();
4121
+ let entries;
4122
+ try { entries = fs.readdirSync(cur, { withFileTypes: true }); } catch { continue; }
4123
+ for (const ent of entries) {
4124
+ if (skipDirs.has(ent.name) || ent.name.startsWith('.')) continue;
4125
+ const abs = path.join(cur, ent.name);
4126
+ if (ent.isDirectory()) { stack.push(abs); continue; }
4127
+ const ext = path.extname(ent.name).toLowerCase();
4128
+ if (ext === '.html' || ext === '.htm') result.htmlFiles.push(abs);
4129
+ else if (ext === '.css') result.cssFiles.push(abs);
4130
+ }
4131
+ }
4132
+
4133
+ if (result.htmlFiles.length === 0) return result;
4134
+
4135
+ // Extract HTML classes/ids
4136
+ const htmlSelectorSet = new Set();
4137
+ for (const htmlAbs of result.htmlFiles) {
4138
+ let html;
4139
+ try { html = fs.readFileSync(htmlAbs, 'utf-8'); } catch { continue; }
4140
+ // Count tags
4141
+ const tagMatches = html.match(/<[a-zA-Z][a-zA-Z0-9-]*/g) || [];
4142
+ result.tagCount += tagMatches.length;
4143
+ // Count img tags specifically
4144
+ result.imgCount += (html.match(/<img\b/gi) || []).length;
4145
+ // Extract classes
4146
+ const classRe = /\bclass\s*=\s*["']([^"']+)["']/g;
4147
+ let m;
4148
+ while ((m = classRe.exec(html)) !== null) {
4149
+ for (const cls of m[1].split(/\s+/)) {
4150
+ if (cls.trim()) htmlSelectorSet.add('.' + cls.trim());
4151
+ }
4152
+ }
4153
+ // Extract IDs
4154
+ const idRe = /\bid\s*=\s*["']([^"']+)["']/g;
4155
+ while ((m = idRe.exec(html)) !== null) {
4156
+ if (m[1].trim()) htmlSelectorSet.add('#' + m[1].trim());
4157
+ }
4158
+ }
4159
+ result.htmlSelectors = [...htmlSelectorSet];
4160
+
4161
+ if (result.htmlSelectors.length === 0) {
4162
+ result.coverage = 1;
4163
+ return result;
4164
+ }
4165
+
4166
+ // Extract CSS selectors (simple: split on { and look at preceding token)
4167
+ for (const cssAbs of result.cssFiles) {
4168
+ let css;
4169
+ try { css = fs.readFileSync(cssAbs, 'utf-8'); } catch { continue; }
4170
+ // Strip comments
4171
+ css = css.replace(/\/\*[\s\S]*?\*\//g, '');
4172
+ // Find rule blocks: anything before { ... }
4173
+ const ruleRe = /([^{}]+)\{[^{}]*\}/g;
4174
+ let m;
4175
+ while ((m = ruleRe.exec(css)) !== null) {
4176
+ result.cssRuleCount++;
4177
+ const selectorList = m[1].trim();
4178
+ if (selectorList.startsWith('@')) continue; // @media, @keyframes etc.
4179
+ // Split combined selectors (.a, .b, .c) and extract bare class/id tokens
4180
+ for (const sel of selectorList.split(',')) {
4181
+ const trimmed = sel.trim();
4182
+ // Find all .classname and #id tokens in this selector
4183
+ const classM = trimmed.match(/\.[\w-]+/g) || [];
4184
+ const idM = trimmed.match(/#[\w-]+/g) || [];
4185
+ for (const t of [...classM, ...idM]) result.cssSelectors.add(t);
4186
+ // Track if any rule targets `img`
4187
+ if (/(^|[\s,>+~])img(\s*[.{#:]|\s*$)/.test(trimmed)) {
4188
+ if (css.slice(m.index, m.index + m[0].length).match(/max-width|object-fit/)) {
4189
+ result.hasImgRule = true;
4190
+ }
4191
+ }
4192
+ }
4193
+ }
4194
+ }
4195
+
4196
+ // Compute coverage
4197
+ for (const sel of result.htmlSelectors) {
4198
+ if (!result.cssSelectors.has(sel)) result.missing.push(sel);
4199
+ }
4200
+ result.coverage = (result.htmlSelectors.length - result.missing.length) / result.htmlSelectors.length;
4201
+
4202
+ return result;
4203
+ }
4204
+
4205
+ /**
4206
+ * Auto-extend CSS by calling LLM when coverage is below threshold or layout
4207
+ * is clearly broken (e.g. <img> tags with no max-width rule). No user prompt;
4208
+ * triggers automatically in pre-flight.
4209
+ */
4210
+ export async function _autoExtendStylesIfNeeded(projectName, config, emit, opts) {
4211
+ opts = opts || {};
4212
+ const minCoverage = opts.minCoverage ?? 0.6;
4213
+ const dir = ProjectStore.dir(projectName);
4214
+ if (!fs.existsSync(dir)) return { extended: false, reason: 'project not found' };
4215
+
4216
+ const analysis = _analyzeCssCoverage(dir);
4217
+ if (analysis.htmlSelectors.length === 0) return { extended: false, reason: 'no HTML selectors' };
4218
+
4219
+ const needsExtend =
4220
+ analysis.coverage < minCoverage ||
4221
+ (analysis.imgCount >= 2 && !analysis.hasImgRule) ||
4222
+ (analysis.tagCount > 50 && analysis.cssRuleCount < 20);
4223
+
4224
+ if (!needsExtend) {
4225
+ return {
4226
+ extended: false,
4227
+ reason: 'coverage acceptable',
4228
+ coverage: analysis.coverage,
4229
+ missing: analysis.missing.length,
4230
+ };
4231
+ }
4232
+
4233
+ // Find the primary CSS file to extend (largest non-placeholder)
4234
+ const cssCandidates = [];
4235
+ for (const cssAbs of analysis.cssFiles) {
4236
+ try {
4237
+ const content = fs.readFileSync(cssAbs, 'utf-8');
4238
+ if (/nha-webcraft:.*placeholder/i.test(content.slice(0, 200))) continue;
4239
+ cssCandidates.push({ abs: cssAbs, size: content.length, content });
4240
+ } catch {}
4241
+ }
4242
+ cssCandidates.sort((a, b) => b.size - a.size);
4243
+ if (cssCandidates.length === 0) return { extended: false, reason: 'no real CSS files to extend' };
4244
+
4245
+ const target = cssCandidates[0];
4246
+ const targetRel = path.relative(dir, target.abs).replace(/\\/g, '/');
4247
+
4248
+ // Build prompt with HTML samples + current CSS + missing selectors
4249
+ let htmlSample = '';
4250
+ for (const htmlAbs of analysis.htmlFiles.slice(0, 3)) {
4251
+ try {
4252
+ const c = fs.readFileSync(htmlAbs, 'utf-8');
4253
+ htmlSample += `### ${path.relative(dir, htmlAbs)}\n${c.slice(0, 3500)}\n\n`;
4254
+ } catch {}
4255
+ }
4256
+
4257
+ const sys = 'You are an expert frontend designer. Extend the existing CSS to cover ALL classes/ids found in the HTML, maintaining the existing design language (colors, gradients, typography, spacing). Add responsive mobile-first rules. Output ONLY the complete extended CSS file content — no markdown fences, no explanations.';
4258
+ const user =
4259
+ `Extend this CSS file: \`${targetRel}\`\n\n` +
4260
+ `Current CSS (${target.size} bytes, ${analysis.cssRuleCount} rules):\n\`\`\`css\n${target.content.slice(0, 6000)}\n\`\`\`\n\n` +
4261
+ `Missing selectors (${analysis.missing.length}):\n${analysis.missing.slice(0, 50).join(', ')}\n\n` +
4262
+ `HTML files for context:\n${htmlSample}\n\n` +
4263
+ `Output the COMPLETE extended CSS file (existing rules + new rules for all missing selectors). Add:\n` +
4264
+ `- img { max-width: 100%; height: auto; object-fit: cover; }\n` +
4265
+ `- Layout for each missing class/id, coherent with the existing design language\n` +
4266
+ `- Responsive breakpoints at 768px and 480px\n` +
4267
+ `- Hover/transition states for interactive elements`;
4268
+
4269
+ if (emit) emit({ type: 'status', msg: `CSS coverage ${(analysis.coverage * 100).toFixed(0)}% (${analysis.missing.length} selectors missing). Auto-extending ${targetRel} via LLM...` });
4270
+
4271
+ let body = '';
4272
+ try {
4273
+ await callLLMStream(config, sys, user, (chunk) => { body += chunk; }, { max_tokens: 8192 });
4274
+ } catch (e) {
4275
+ if (emit) emit({ type: 'warn', msg: `CSS extend failed: ${(e.message || e).slice(0, 200)}` });
4276
+ return { extended: false, reason: 'llm_failed', error: e.message };
4277
+ }
4278
+
4279
+ // Strip markdown fences
4280
+ body = body.replace(/^```[a-zA-Z]*\n?/m, '').replace(/\n?```\s*$/m, '').trim();
4281
+ if (_looksLikeLLMError(body) || body.length < target.size * 0.5) {
4282
+ if (emit) emit({ type: 'warn', msg: `CSS extend produced suspicious output (${body.length} bytes vs ${target.size} original) — keeping original.` });
4283
+ return { extended: false, reason: 'output_too_short_or_error' };
4284
+ }
4285
+
4286
+ // Backup + write
4287
+ try {
4288
+ fs.writeFileSync(target.abs + '.before-extend-' + Date.now(), target.content, 'utf-8');
4289
+ fs.writeFileSync(target.abs, body, 'utf-8');
4290
+ } catch (e) {
4291
+ return { extended: false, reason: 'write_failed', error: e.message };
4292
+ }
4293
+
4294
+ // Re-analyze to confirm improvement
4295
+ const after = _analyzeCssCoverage(dir);
4296
+ if (emit) emit({ type: 'status', msg: `CSS extended: ${targetRel} ${target.size} → ${body.length} bytes. Coverage ${(analysis.coverage * 100).toFixed(0)}% → ${(after.coverage * 100).toFixed(0)}%.` });
4297
+
4298
+ return {
4299
+ extended: true,
4300
+ file: targetRel,
4301
+ sizeBefore: target.size,
4302
+ sizeAfter: body.length,
4303
+ coverageBefore: analysis.coverage,
4304
+ coverageAfter: after.coverage,
4305
+ missingBefore: analysis.missing.length,
4306
+ missingAfter: after.missing.length,
4307
+ };
4308
+ }
4309
+
4003
4310
  export function autoRepairProject(projectName) {
4004
4311
  const dir = ProjectStore.dir(projectName);
4005
4312
  if (!fs.existsSync(dir)) throw new Error('project not found');
@@ -853,8 +853,8 @@ Errore: `+e.msg,L(!1))}catch{}}}}catch(e){e instanceof DOMException&&e.name===`A
853
853
  `);i=a.pop()??``;for(let e of a){let t=e.replace(/^data: /,``).trim();if(t)try{let e=JSON.parse(t);(e.type===`phase`||e.type===`status`||e.type===`log`||e.type===`warn`||e.type===`error`)&&_t(t=>[...t.slice(-49),{kind:e.type,msg:String(e.msg||``),ts:Date.now()}]),e.type===`ready`&&e.port?(lt(e.port),pt(!1)):e.type===`error`&&ht(e.msg)}catch{}}}}catch(e){ht(e.message||`Connection failed`)}pt(!1)}}async function Gt(){c&&await Wt(c)}(0,_.useEffect)(()=>{z.current=V,Ot.current=Gt});async function Kt(){if(!et||!c)return;let e=await E(`/api/studio/webcraft/grep`,{projectName:c,query:et});e?.matches&&rt(e.matches)}function qt(e){let t=g.findIndex(t=>t.name===e);t>=0&&(b(t),i(`files`))}function Jt(){window.open(`/api/studio/webcraft/download/${encodeURIComponent(c)}`,`_blank`)}async function Yt(e,t,n,r){t.endsWith(`.md`)||(t+=`.md`);let i={name:t,content:n,type:r},a;a=e.mode===`edit`&&e.idx!==null?We.map((t,n)=>n===e.idx?i:t):[...We,i],Ge(a),Ye(null);let o=c||`MyProject`;c||l(o),await E(`/api/studio/webcraft/skills/${encodeURIComponent(o)}`,{skills:a})}async function Xt(e){let t=We[e];!t||!confirm(`Eliminare "${t.name}"?`)||(await E(`/api/studio/webcraft/skills/${encodeURIComponent(c)}/delete`,{name:t.name}),Ge(We.filter((t,n)=>n!==e)))}async function Zt(e){let t=We[e];if(!t||!confirm(`Svuotare "${t.name}"? Il file rimane ma il contenuto viene cancellato.`))return;let n=We.map((t,n)=>n===e?{...t,content:``}:t);Ge(n),await E(`/api/studio/webcraft/skills/${encodeURIComponent(c)}`,{skills:n})}async function Qt(){let e=await T(`/api/studio/webcraft/projects`);e?.projects&&st(e.projects)}async function H(e){let t=await T(`/api/studio/webcraft/projects/load/${encodeURIComponent(e.name)}`);if(!t)return;let r=t.projectName??e.name;l(r),d(t.description??``),v(t.files??[]),b(0),n(`new`),i(`files`),Ie([]),Ge([]),qe(!1),pe([]),kt(r);let a=await T(`/api/studio/webcraft/projects/chat/load/${encodeURIComponent(r)}`);a?.chat&&Ie(a.chat);let o=await T(`/api/studio/webcraft/skills/${encodeURIComponent(r)}`);o?.skills&&(Ge(o.skills),qe(!0))}async function $t(e){confirm(`Eliminare: ${e.name} - ${e.dir}?`)&&(await E(`/api/studio/webcraft/projects/${encodeURIComponent(e.name)}`,{},`DELETE`),st(ot.filter(t=>t.name!==e.name)),c===e.name&&(l(``),v([]),Ie([]),d(``)))}async function en(){if(!He)return;let e=He.originalMessage;Ue(null),await Ft(e+`
854
854
  [Piano approvato — procedi con le modifiche]`,null,[])}function tn(e){e&&Array.from(e).forEach(e=>{let t=new FileReader;t.onload=t=>{let n=(t.target?.result).split(`,`)[1];Ve(t=>[...t,{name:e.name,mimeType:e.type,base64:n,size:e.size}])},t.readAsDataURL(e)})}let nn=c&&g.length>0,rn=g[y],U=ze||N;return(0,k.jsxs)(`div`,{className:Q.root,children:[(0,k.jsxs)(`div`,{className:Q.header,children:[(0,k.jsxs)(`div`,{children:[(0,k.jsxs)(`div`,{className:Q.title,children:[`⚙ WebCraft`,c?` — ${c}`:``]}),!c&&(0,k.jsx)(`div`,{className:Q.subtitle,children:`Genera progetti web completi con agenti AI`})]}),(0,k.jsxs)(`div`,{className:Q.headerTabs,children:[(0,k.jsx)(`button`,{className:`${Q.tabBtn} ${t===`new`?Q.tabActive:``}`,onClick:async()=>{if(!(ee.size>0&&!confirm(`${ee.size} unsaved file(s). Discard changes and create new project?`))){if(ct){try{await fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`})}catch{}lt(null)}v([]),b(0),S(null),w(null),te(new Set),Ie([]),l(``),d(``),Re(``),Ge([]),qe(!1),Pe(null),Ce(!1),L(!1),oe(!1),ce(null),n(`new`)}},children:`+ Nuovo`}),(0,k.jsx)(`button`,{className:`${Q.tabBtn} ${t===`projects`?Q.tabActive:``}`,onClick:()=>{n(`projects`),Qt()},children:`📁 Progetti`})]})]}),(0,k.jsx)(`div`,{className:Q.body,children:t===`projects`?(0,k.jsx)(`div`,{className:Q.projectsList,children:ot.length===0?(0,k.jsxs)(`div`,{className:Q.emptyProjects,children:[(0,k.jsx)(`span`,{className:Q.emptyIcon,children:`📁`}),(0,k.jsx)(`span`,{children:e(`webcraft.noProjects`)}),(0,k.jsx)(`span`,{className:Q.emptyHint,children:`Crea un progetto nella tab Nuovo`})]}):ot.map(e=>(0,k.jsxs)(`div`,{className:Q.projectCard,children:[(0,k.jsxs)(`div`,{className:Q.projectInfo,children:[(0,k.jsx)(`div`,{className:Q.projectName,children:e.name}),(0,k.jsx)(`div`,{className:Q.projectDesc,children:e.description}),(0,k.jsxs)(`div`,{className:Q.projectMeta,children:[(0,k.jsxs)(`span`,{children:[`📄 `,e.fileCount,` file`]}),(0,k.jsxs)(`span`,{children:[`📅 `,e.createdAt?new Date(e.createdAt).toLocaleString():``]})]})]}),(0,k.jsx)(`button`,{className:Q.openBtn,onClick:()=>H(e),children:`↗ Apri`}),(0,k.jsx)(`button`,{className:Q.deleteBtn,onClick:()=>$t(e),children:`🗑`})]},e.name))}):(0,k.jsxs)(`div`,{className:Q.editor,"data-mobile-view":a?o:void 0,children:[(0,k.jsxs)(`div`,{className:Q.examples,children:[(0,k.jsx)(`div`,{className:Q.sectionLabel,children:`Esempi`}),(0,k.jsx)(`div`,{className:Q.examplePills,children:eE.map(e=>(0,k.jsx)(`button`,{className:Q.examplePill,onClick:()=>{l(e.name),d(e.desc),Re(e.desc)},children:e.name},e.name))})]}),(0,k.jsxs)(`div`,{className:Q.editorCols,children:[(0,k.jsxs)(`div`,{className:Q.leftSidebar,children:[(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`Blocchi`}),tE.map(e=>(0,k.jsxs)(`label`,{className:Q.blockLabel,children:[(0,k.jsx)(`input`,{type:`checkbox`,checked:f[e.key],onChange:t=>p(n=>({...n,[e.key]:t.target.checked})),className:Q.blockCheck}),(0,k.jsx)(`span`,{children:e.icon}),(0,k.jsx)(`span`,{children:e.label})]},e.key))]}),f.auth&&(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsxs)(`div`,{className:Q.panelHeader,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`Campi Auth`}),(0,k.jsx)(`button`,{className:Q.addBtn,onClick:()=>h(e=>[...e,{label:`New field`,type:`text`,required:!1}]),children:`+ Campo`})]}),m.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.authField,children:[(0,k.jsx)(`input`,{value:e.label,onChange:e=>h(n=>n.map((n,r)=>r===t?{...n,label:e.target.value}:n)),className:Q.authFieldInput}),(0,k.jsx)(`select`,{value:e.type,onChange:e=>h(n=>n.map((n,r)=>r===t?{...n,type:e.target.value}:n)),className:Q.authFieldSelect,children:[`text`,`email`,`password`,`tel`,`date`,`number`].map(e=>(0,k.jsx)(`option`,{value:e,children:e},e))}),(0,k.jsx)(`input`,{type:`checkbox`,checked:e.required,onChange:e=>h(n=>n.map((n,r)=>r===t?{...n,required:e.target.checked}:n)),title:`Required`,className:Q.authFieldReq}),(0,k.jsx)(`button`,{onClick:()=>h(e=>e.filter((e,n)=>n!==t)),className:Q.removeFieldBtn,children:`×`})]},t))]}),(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsxs)(`div`,{className:Q.panelHeader,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`🗂 Contesto AI`}),(0,k.jsx)(`button`,{className:Q.addBtn,onClick:()=>Ye({mode:`new`,idx:null,name:``,content:``,type:`skill`,generating:!1}),children:`+ Skill`})]}),We.length>0?(0,k.jsx)(`div`,{className:Q.skillsList,children:We.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.skillRow,children:[(0,k.jsx)(`span`,{className:Q.skillIcon,children:aE(e.type)}),(0,k.jsx)(`span`,{className:Q.skillName,title:e.name,children:e.name}),(0,k.jsx)(`span`,{className:`${Q.skillBadge} ${Q[`skillBadge_`+e.type]}`,children:e.type}),!e.content&&e.type!==`log`&&(0,k.jsx)(`span`,{className:Q.skillEmpty,children:`⚠`}),(0,k.jsx)(`button`,{className:Q.skillBtn,onClick:()=>Ye({mode:e.type===`log`?`view`:`edit`,idx:t,name:e.name,content:e.content,type:e.type,generating:!1}),children:e.type===`log`?`👁`:`✏`}),e.type!==`memory`&&e.type!==`provider`&&e.type!==`log`&&(0,k.jsx)(`button`,{className:Q.skillBtn,onClick:()=>Zt(t),children:`🗑`}),e.type===`log`&&(0,k.jsx)(`button`,{className:Q.skillBtn,onClick:()=>Xt(t),children:`🗑`})]},t))}):(0,k.jsx)(`div`,{className:Q.skillsEmpty,children:Ke?`Nessun file di contesto. Clicca "+ Skill" per aggiungerne uno.`:`Crea o carica un progetto per i file di contesto.`})]}),Xe.length>0&&(0,k.jsxs)(`div`,{className:Q.panel,children:[(0,k.jsx)(`div`,{className:Q.panelTitle,children:`💾 Snapshot`}),Xe.slice(0,5).map(e=>{let t=e.ts.replace(`T`,` `).slice(0,16);return(0,k.jsxs)(`div`,{className:Q.snapshotRow,children:[(0,k.jsx)(`span`,{className:Q.snapshotTs,children:t}),(0,k.jsxs)(`span`,{className:Q.snapshotCount,children:[e.fileCount,`f`]}),(0,k.jsx)(`button`,{className:Q.snapshotBtn,onClick:()=>Vt(e.ts),children:`↺`})]},e.ts)})]}),N&&(0,k.jsx)(`div`,{className:Q.genStatus,children:`⏳ Generazione...`}),we&&(0,k.jsxs)(`div`,{className:Q.repairStatus,children:[(0,k.jsx)(`div`,{className:Q.repairStatusTitle,children:`🔧 Correzione automatica...`}),(0,k.jsxs)(`div`,{className:Q.repairStatusProg,children:[Ee,` / `,De,` file`]}),(0,k.jsx)(`div`,{className:Q.repairStatusFile,children:ke})]}),g.length>0&&!N&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:Q.actionRow,children:[(0,k.jsx)(`button`,{className:Q.actionBtn,onClick:Jt,children:`⬇ ZIP`}),(0,k.jsx)(`button`,{className:Q.actionBtnIcon,title:`Syntax check`,onClick:Ht,children:`✅`}),(0,k.jsx)(`button`,{className:`${Q.actionBtnIcon} ${Qe?Q.actionBtnActive:``}`,title:`Grep`,onClick:()=>$e(!Qe),children:`🔍`}),(0,k.jsx)(`button`,{className:Q.actionBtnIcon,title:`Snapshot`,onClick:zt,children:`💾`})]}),g.some(e=>e._error||e._syntaxError)&&!we&&(0,k.jsx)(`button`,{className:Q.repairBtn,onClick:V,children:`🔧 Correggi tutti i file rossi`}),(0,k.jsx)(`button`,{className:Q.sandboxBtn,onClick:()=>{c?Wt(c):i(`preview`)},children:ft?`⏳ Starting...`:ct?`🌐 Sandbox Live`:`▶ Sandbox`}),Ne&&(0,k.jsxs)(`div`,{className:Q.statsBar,children:[(0,k.jsxs)(`span`,{children:[`⏱ `,Ne.seconds>=60?`${Math.floor(Ne.seconds/60)}m ${Ne.seconds%60}s`:`${Ne.seconds}s`]}),(0,k.jsxs)(`span`,{children:[`↑ `,Ne.tokIn.toLocaleString(),` tok`]}),(0,k.jsxs)(`span`,{children:[`↓ `,Ne.tokOut.toLocaleString(),` tok`]}),(0,k.jsxs)(`span`,{children:[`📄 `,Ne.files,` file`]})]})]})]}),(0,k.jsxs)(`div`,{className:Q.rightPanel,children:[(0,k.jsxs)(`div`,{className:Q.rightTabBar,children:[(0,k.jsx)(`button`,{className:`${Q.rightTab} ${r===`preview`?``:Q.rightTabActive}`,onClick:()=>i(`files`),children:`📄 File`}),(0,k.jsx)(`button`,{className:`${Q.rightTab} ${r===`preview`?Q.rightTabActive:``}`,onClick:()=>i(`preview`),children:`🌐 Sandbox`})]}),we&&(0,k.jsxs)(`div`,{className:Q.repairBar,children:[(0,k.jsxs)(`div`,{className:Q.repairBarRow,children:[(0,k.jsx)(`span`,{className:Q.repairBarIcon,children:`🔧`}),(0,k.jsx)(`span`,{className:Q.repairBarLabel,children:`Auto-fix`}),(0,k.jsx)(`span`,{className:Q.repairBarFile,children:ke}),(0,k.jsxs)(`span`,{className:Q.repairBarCounter,children:[Ee,` / `,De]}),(0,k.jsx)(`span`,{className:Q.repairBarTime,children:R}),(0,k.jsx)(`button`,{className:Q.stopBtn,onClick:Lt,children:`⏹ Stop`})]}),(0,k.jsx)(`div`,{className:Q.progressTrack,children:(0,k.jsx)(`div`,{className:Q.repairProgress,style:{width:De>0?`${Math.round(Ee/De*100)}%`:`0%`}})})]}),N&&(0,k.jsxs)(`div`,{className:Q.genBar,children:[(0,k.jsxs)(`div`,{className:Q.genBarRow,children:[(0,k.jsx)(`span`,{className:Q.genBarRobot,children:`🤖`}),(0,k.jsx)(`span`,{className:Q.genBarLabel,children:I.total===0?`Pianificazione...`:`Generazione`}),(0,k.jsx)(`span`,{className:Q.genBarFile,children:(I.name||``).split(`,`)[0].trim()}),(0,k.jsx)(`span`,{className:Q.genBarCounter,children:I.total>0?`${I.fi} / ${I.total}`:``}),(0,k.jsx)(`span`,{className:Q.genBarCounter,children:je.tokIn+je.tokOut>0?`↑${jt(je.tokIn)} ↓${jt(je.tokOut)}`:``}),(0,k.jsx)(`span`,{className:Q.genBarTime,children:vt}),(0,k.jsxs)(`span`,{className:Q.genDots,children:[(0,k.jsx)(`span`,{className:`${Q.dot} ${Q.dot1}`}),(0,k.jsx)(`span`,{className:`${Q.dot} ${Q.dot2}`}),(0,k.jsx)(`span`,{className:`${Q.dot} ${Q.dot3}`})]})]}),(0,k.jsx)(`div`,{className:Q.progressTrack,children:(0,k.jsx)(`div`,{className:Q.genProgress,style:{width:I.total>0?`${Math.round(I.fi/I.total*100)}%`:`0%`}})})]}),r===`preview`?(0,k.jsxs)(`div`,{className:Q.sandboxWrap,children:[(0,k.jsxs)(`div`,{className:Q.sandboxStatusBar,children:[(0,k.jsx)(`span`,{className:Q.sandboxStatusDot,style:{background:ct?`#4ade80`:ft?`#facc15`:`#64748b`}}),(0,k.jsx)(`span`,{className:Q.sandboxStatusText,children:ct?`Live :${ct}`:ft?`Starting...`:`Stopped`}),ct&&(0,k.jsx)(`button`,{className:Q.sandboxReloadBtn,onClick:()=>{let e=document.querySelector(`iframe[title="WebCraft Sandbox"]`);e&&(e.src=e.src)},children:`↻`}),ct&&(0,k.jsx)(`button`,{className:Q.sandboxStopBtn,onClick:async()=>{lt(null),M([]);try{await fetch(`/api/studio/webcraft/sandbox`,{method:`DELETE`})}catch{}},children:`⏹`}),!ct&&!ft&&(0,k.jsx)(`button`,{className:Q.sandboxStartBtnSmall,onClick:()=>{c&&Wt(c)},children:`▶ Start`})]}),de.length>0&&(0,k.jsxs)(`div`,{className:Q.runtimeErrors,children:[(0,k.jsxs)(`div`,{className:Q.runtimeErrorsHeader,children:[(0,k.jsxs)(`span`,{children:[`❌ `,de.length,` runtime error`,de.length>1?`s`:``]}),(0,k.jsx)(`button`,{className:Q.runtimeErrorsFix,onClick:()=>{let e=`Fix these runtime errors. Use edit_file (or create_file if the file doesn't exist) to actually modify the source — do NOT respond with text descriptions. After each fix, call check_syntax to verify. If edit_file fails because old_text doesn't match, read_file again and retry with the exact text.\n\n${de.map(e=>`${e.message} (${e.source||``}:${e.line||0})`).join(`
855
855
  `)}`;fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),M([]),i(`files`),Pt(e)},children:`🔧 Auto-fix`}),(0,k.jsx)(`button`,{className:Q.runtimeErrorsDismiss,onClick:()=>{fetch(`/api/studio/webcraft/sandbox/errors`,{method:`DELETE`}),M([])},children:`✕`})]}),de.slice(0,3).map((e,t)=>(0,k.jsxs)(`div`,{className:Q.runtimeErrorLine,children:[e.message,e.source?` — ${e.source.split(`/`).pop()}:${e.line}`:``]},t))]}),ct?(0,k.jsx)(`iframe`,{src:`http://localhost:${ct}`,className:Q.sandboxFrame,title:`WebCraft Sandbox`,sandbox:`allow-scripts allow-same-origin allow-forms allow-popups`}):(0,k.jsxs)(`div`,{className:Q.sandboxEmpty,children:[(0,k.jsx)(`span`,{style:{fontSize:48},children:ft?`⏳`:mt?`❌`:`🌐`}),(0,k.jsx)(`span`,{style:{fontWeight:700,fontSize:16},children:ft?`Starting sandbox...`:mt?`Sandbox Error`:`Preview`}),mt&&(0,k.jsx)(`pre`,{style:{fontSize:11,maxWidth:600,textAlign:`left`,color:`#f87171`,background:`rgba(248,113,113,0.08)`,border:`1px solid rgba(248,113,113,0.2)`,borderRadius:6,padding:`8px 12px`,whiteSpace:`pre-wrap`,wordBreak:`break-word`,margin:`8px 0`,lineHeight:1.5,maxHeight:200,overflow:`auto`},children:mt}),gt.length>0&&(0,k.jsxs)(`div`,{style:{width:`100%`,maxWidth:720,margin:`8px 0`,textAlign:`left`},children:[(0,k.jsxs)(`div`,{style:{fontSize:10,color:`#94a3b8`,textTransform:`uppercase`,letterSpacing:`0.5px`,marginBottom:4},children:[`Sandbox log (`,gt.length,`)`]}),(0,k.jsx)(`pre`,{style:{fontSize:11,lineHeight:1.5,fontFamily:`SF Mono, Monaco, monospace`,background:`rgba(15, 23, 42, 0.6)`,border:`1px solid rgba(148, 163, 184, 0.18)`,borderRadius:6,padding:`8px 12px`,maxHeight:240,overflow:`auto`,whiteSpace:`pre-wrap`,wordBreak:`break-word`,color:`#cbd5e1`},children:gt.slice(-40).map((e,t)=>(0,k.jsx)(`div`,{style:{color:e.kind===`error`?`#f87171`:e.kind===`warn`?`#fbbf24`:e.kind===`phase`?`#60a5fa`:e.kind===`status`?`#34d399`:`#94a3b8`},children:`[${e.kind}] ${e.msg}`},t))})]}),!ft&&(0,k.jsxs)(`button`,{className:Q.sandboxStartBtn,onClick:()=>{c&&Wt(c)},children:[`▶ `,mt?`Retry`:`Start Sandbox`]})]})]}):(0,k.jsx)(`div`,{className:Q.codeArea,children:g.length===0&&N?(0,k.jsx)(`div`,{className:Q.noFiles,children:(0,k.jsxs)(`div`,{className:Q.noFilesHero,children:[(0,k.jsx)(`span`,{className:Q.noFilesIcon,children:`⏳`}),(0,k.jsx)(`span`,{className:Q.noFilesTitle,children:`Pianificazione...`}),(0,k.jsx)(`span`,{className:Q.noFilesTagline,children:I.name||`Analisi della struttura del progetto in corso`})]})}):g.length===0?(0,k.jsxs)(`div`,{className:Q.noFiles,children:[(0,k.jsxs)(`div`,{className:Q.noFilesHero,children:[(0,k.jsx)(`span`,{className:Q.noFilesIcon,children:`🔨`}),(0,k.jsx)(`span`,{className:Q.noFilesTitle,children:`WebCraft`}),(0,k.jsx)(`span`,{className:Q.noFilesTagline,children:`Genera progetti web completi con AI`})]}),(0,k.jsxs)(`div`,{className:Q.noFilesSteps,children:[(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`1`}),(0,k.jsx)(`span`,{children:`Scegli un esempio o scrivi una descrizione nel box in basso`})]}),(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`2`}),(0,k.jsxs)(`span`,{children:[`Premi `,(0,k.jsx)(`strong`,{children:`▶ Genera`}),` — l'AI crea tutti i file del progetto`]})]}),(0,k.jsxs)(`div`,{className:Q.noFilesStep,children:[(0,k.jsx)(`span`,{className:Q.noFilesStepNum,children:`3`}),(0,k.jsx)(`span`,{children:`Chiedi modifiche in chat, scarica lo ZIP o avvia il Sandbox`})]})]}),(0,k.jsxs)(`div`,{className:Q.noFilesExamplesHint,children:[`💡 Prova: `,(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[0];l(e.name),d(e.desc),Re(e.desc)},children:`MySaaS`}),(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[1];l(e.name),d(e.desc),Re(e.desc)},children:`MyShop`}),(0,k.jsx)(`button`,{className:Q.noFilesExampleBtn,onClick:()=>{let e=eE[3];l(e.name),d(e.desc),Re(e.desc)},children:`MyPortfolio`})]})]}):(0,k.jsxs)(`div`,{className:Q.codeLayout,children:[(0,k.jsx)(`div`,{className:Q.ideTabBar,children:g.map((e,t)=>{let n=e._error||!!e._syntaxError,r=t===y;return(0,k.jsxs)(`button`,{className:`${Q.ideTab} ${r?Q.ideTabActive:``} ${n?Q.ideTabError:``} ${e._pending?Q.ideTabPending:``}`,onClick:()=>{b(t),w(null),S(null),N&&me.current!==null&&t!==me.current&&(he.current&&clearTimeout(he.current),he.current=setTimeout(()=>{me.current!==null&&(b(me.current),S(null))},1e4))},title:e.name,children:[(0,k.jsx)(`span`,{className:Q.ideTabIcon,children:e._pending?`⌛`:n?`⚠`:rE(e.name)}),(0,k.jsx)(`span`,{className:Q.ideTabName,children:(e.name||``).split(`/`).pop()}),ee.has(e.name)&&(0,k.jsx)(`span`,{className:Q.ideTabUnsaved,children:`●`}),n&&(0,k.jsx)(`span`,{className:Q.ideTabDot})]},t)})}),xe&&(0,k.jsxs)(`div`,{className:Q.diffOverlay,children:[(0,k.jsxs)(`div`,{className:Q.diffOverlayHeader,children:[(0,k.jsxs)(`span`,{children:[`✏ Modifica proposta — `,(0,k.jsx)(`strong`,{children:xe.file})]}),(0,k.jsxs)(`div`,{className:Q.diffOverlayActions,children:[(0,k.jsx)(`button`,{className:Q.diffAcceptBtn,onClick:()=>{v(e=>e.map(e=>e.name===xe.file?{...e,content:xe.after}:e)),Se(null)},children:`✓ Accetta`}),(0,k.jsx)(`button`,{className:Q.diffRejectBtn,onClick:()=>Se(null),children:`✕ Rifiuta`})]})]}),(0,k.jsx)(`div`,{className:Q.diffOverlayBody,children:(0,k.jsx)(uE,{before:xe.before,after:xe.after})})]}),(0,k.jsxs)(`div`,{className:Q.codeRow,children:[(0,k.jsxs)(`div`,{className:Q.fileTreeWrap,children:[fe.length>0&&(0,k.jsxs)(`div`,{className:Q.scanBanner,children:[(0,k.jsx)(`span`,{className:Q.scanBannerIcon,children:`⚠`}),(0,k.jsxs)(`span`,{className:Q.scanBannerText,children:[fe.length,` issue`,fe.length>1?`s`:``]}),(0,k.jsx)(`button`,{className:Q.scanBannerFix,onClick:async()=>{i(`files`);try{let e=await E(`/api/studio/webcraft/auto-repair`,{projectName:c});if(e?.summary){let t=(e.repairs||[]).slice(0,20).map(e=>` • ${e.file} (${e.kind}${e.source?` from ${e.source}`:``})`).join(`
856
- `);Ie(n=>[...n,{role:`agent`,text:`🔧 Auto-repair: ${e.summary}\n${t}`,tools:[]}])}let t=await E(`/api/studio/webcraft/complete`,{projectName:c}),n=t?.siblingFills?.length||0,r=t?.llmFills?.length||0,i=t?.stubFallbacks?.length||0;if(n+r+i>0){let e=[];n&&e.push(...(t.siblingFills||[]).map(e=>` • ${e.target} ${e.source} (${e.size} bytes, copied)`)),r&&e.push(...(t.llmFills||[]).map(e=>` • ${e.target} (${e.length} bytes, LLM-generated)`)),i&&e.push(...(t.stubFallbacks||[]).map(e=>` • ${e} (stub fallback — LLM unavailable)`)),Ie(t=>[...t,{role:`agent`,text:`✨ Asset completion: ${n} from siblings, ${r} via LLM${i?`, ${i} as stubs`:``}\n${e.join(`
857
- `)}`,tools:[]}])}await It([...e?.filesRepaired||[],...e?.filesCreated||[],...(t?.siblingFills||[]).map(e=>e.target),...(t?.llmFills||[]).map(e=>e.target)],{}),await kt(c)}catch(e){console.error(`[fix] failed:`,e)}},children:`Fix`})]}),(0,k.jsx)(WT,{files:g,activeIndex:y,unsavedFiles:ee,errorFiles:new Set(fe.filter(e=>e.severity===`error`).map(e=>e.file)),onSelect:e=>{b(e),w(null),S(null),N&&me.current!==null&&e!==me.current&&(he.current&&clearTimeout(he.current),he.current=setTimeout(()=>{me.current!==null&&(b(me.current),S(null))},1e4))}})]}),(0,k.jsx)(`div`,{className:Q.codeEditorWrap,children:rn&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:Q.codeHeader,children:[(0,k.jsx)(`span`,{className:Q.codeFileIcon,children:rE(rn.name)}),(0,k.jsx)(`span`,{className:Q.codeFileName,children:rn.name}),rn.content&&!rn._error&&(0,k.jsxs)(`span`,{className:Q.codeFileMeta,children:[(rn.content||``).split(`
856
+ `);Ie(n=>[...n,{role:`agent`,text:`🔧 Auto-repair: ${e.summary}\n${t}`,tools:[]}])}let t=await E(`/api/studio/webcraft/complete`,{projectName:c}),n=t?.htmlRewrites?.length||0,r=t?.llmFills?.length||0,i=t?.stubFallbacks?.length||0;if(n+r+i>0){let e=[];n&&e.push(...(t.htmlRewrites||[]).map(e=>` • ${e.from}: ${e.oldHref} ${e.newHref} (point to real file)`)),r&&e.push(...(t.llmFills||[]).map(e=>` • ${e.target} (${e.length} bytes, LLM-generated)`)),i&&e.push(...(t.stubFallbacks||[]).map(e=>` • ${e} (stub fallback — LLM unavailable)`)),Ie(t=>[...t,{role:`agent`,text:`✨ Asset completion: ${n} HTML rewrites, ${r} via LLM${i?`, ${i} as stubs`:``}\n${e.join(`
857
+ `)}`,tools:[]}])}let a=await E(`/api/studio/webcraft/extend-styles`,{projectName:c});a?.extended&&Ie(e=>[...e,{role:`agent`,text:`🎨 Styles auto-extended: ${a.file} (${a.sizeBefore}→${a.sizeAfter} bytes, coverage ${((a.coverageBefore||0)*100).toFixed(0)}%→${((a.coverageAfter||0)*100).toFixed(0)}%, ${(a.missingBefore||0)-(a.missingAfter||0)} new selectors covered)`,tools:[]}]),await It([...e?.filesRepaired||[],...e?.filesCreated||[],...(t?.htmlRewrites||[]).map(e=>e.from),...(t?.llmFills||[]).map(e=>e.target),...a?.file?[a.file]:[]],{}),await kt(c)}catch(e){console.error(`[fix] failed:`,e)}},children:`Fix`})]}),(0,k.jsx)(WT,{files:g,activeIndex:y,unsavedFiles:ee,errorFiles:new Set(fe.filter(e=>e.severity===`error`).map(e=>e.file)),onSelect:e=>{b(e),w(null),S(null),N&&me.current!==null&&e!==me.current&&(he.current&&clearTimeout(he.current),he.current=setTimeout(()=>{me.current!==null&&(b(me.current),S(null))},1e4))}})]}),(0,k.jsx)(`div`,{className:Q.codeEditorWrap,children:rn&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsxs)(`div`,{className:Q.codeHeader,children:[(0,k.jsx)(`span`,{className:Q.codeFileIcon,children:rE(rn.name)}),(0,k.jsx)(`span`,{className:Q.codeFileName,children:rn.name}),rn.content&&!rn._error&&(0,k.jsxs)(`span`,{className:Q.codeFileMeta,children:[(rn.content||``).split(`
858
858
  `).length,` righe · `,iE(rn.content||``)]}),!rn._pending&&!rn._error&&rn.content&&(0,k.jsx)(`button`,{className:`${Q.editToggleBtn} ${C===null?``:Q.editToggleBtnActive}`,onClick:()=>{C===null?w(rn.content):(v(e=>e.map((e,t)=>t===y?{...e,content:C}:e)),E(`/api/studio/webcraft/file/write`,{projectName:c,path:rn.name,content:C}),w(null))},children:C===null?`✏ Modifica`:`💾 Salva`}),(0,k.jsx)(`button`,{className:Q.headerIconBtn,title:`Split view`,onClick:()=>ce(se===null?+(y===0&&g.length>1):null),children:`⫼`}),(0,k.jsx)(`button`,{className:`${Q.headerIconBtn} ${A?Q.headerIconBtnActive:``}`,title:`Terminal`,onClick:()=>oe(!A),children:`⌨`}),(0,k.jsx)(`button`,{className:Q.headerIconBtn,title:`Development Guide`,onClick:()=>ue(!0),children:`📖`})]}),ne&&(0,k.jsxs)(`div`,{className:Q.findBar,children:[(0,k.jsx)(`input`,{className:Q.findInput,value:D,onChange:e=>ie(e.target.value),placeholder:`Find...`,autoFocus:!0}),(0,k.jsx)(`input`,{className:Q.findInput,value:O,onChange:e=>ae(e.target.value),placeholder:`Replace...`}),(0,k.jsx)(`span`,{className:Q.findCount,children:D?((rn.content||``).match(new RegExp(D.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`gi`))?.length||0)+` found`:``}),(0,k.jsx)(`button`,{className:Q.findBtn,onClick:()=>{!D||C===null||w(C.replace(new RegExp(D.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`i`),O))},children:`Replace`}),(0,k.jsx)(`button`,{className:Q.findBtn,onClick:()=>{!D||C===null||w(C.replace(new RegExp(D.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`),`gi`),O))},children:`All`}),(0,k.jsx)(`button`,{className:Q.findClose,onClick:()=>re(!1),children:`×`})]}),rn._error&&(0,k.jsx)(`div`,{className:Q.fileError,children:`⚠ Generazione fallita — chiedi al modello di rigenerare questo file`}),rn._syntaxError&&!rn._error&&(0,k.jsxs)(`div`,{className:Q.fileSyntaxError,children:[`⚠ Syntax error: `,rn._syntaxError]}),N&&x!==null?(0,k.jsx)(`pre`,{className:Q.streamingPre,ref:e=>{e&&ge.current&&(e.scrollTop=e.scrollHeight)},onScroll:e=>{let t=e.currentTarget;t.scrollHeight-t.scrollTop-t.clientHeight<50?ge.current=!0:(ge.current=!1,_e.current&&clearTimeout(_e.current),_e.current=setTimeout(()=>{ge.current=!0},15e3))},dangerouslySetInnerHTML:{__html:QT(x||``,(rn.name.split(`.`).pop()||`js`).toLowerCase())+`<span class="`+Q.streamingCursor+`">▋</span>`}}):(0,k.jsx)(RT,{value:C===null?rn.content||``:C,filename:rn.name,readOnly:C===null,projectName:c,onChange:e=>{w(e),rn&&te(e=>new Set(e).add(rn.name))},onSave:e=>{v(t=>t.map((t,n)=>n===y?{...t,content:e}:t)),E(`/api/studio/webcraft/file/write`,{projectName:c,path:rn.name,content:e}),w(null),te(e=>{let t=new Set(e);return t.delete(rn.name),t})}})]})}),se!==null&&g[se]&&(0,k.jsxs)(`div`,{className:Q.codeEditorWrap,children:[(0,k.jsxs)(`div`,{className:Q.codeHeader,children:[(0,k.jsx)(`span`,{className:Q.codeFileIcon,children:rE(g[se].name)}),(0,k.jsx)(`span`,{className:Q.codeFileName,children:g[se].name}),(0,k.jsx)(`button`,{className:Q.headerIconBtn,onClick:()=>ce(null),children:`✕`})]}),(0,k.jsx)(RT,{value:g[se].content||``,filename:g[se].name,readOnly:!0})]})]}),A&&(0,k.jsxs)(`div`,{className:Q.terminalPanel,children:[(0,k.jsxs)(`div`,{className:Q.terminalHeader,children:[(0,k.jsx)(`span`,{className:Q.terminalTitle,children:`Terminal`}),(0,k.jsx)(`button`,{className:Q.terminalClose,onClick:()=>oe(!1),children:`✕`})]}),(0,k.jsx)(YT,{projectDir:c||void 0})]})]})})]})]})]})}),He&&t!==`projects`&&(0,k.jsxs)(`div`,{className:Q.planBanner,children:[(0,k.jsx)(`div`,{className:Q.planTitle,children:`📌 Piano proposto — approva per eseguire`}),(0,k.jsx)(`pre`,{className:Q.planText,children:He.plan}),(0,k.jsxs)(`div`,{className:Q.planActions,children:[(0,k.jsx)(`button`,{className:Q.planApprove,onClick:en,children:`✓ Esegui`}),(0,k.jsx)(`button`,{className:Q.planReject,onClick:()=>Ue(null),children:`✕ Annulla`})]})]}),Qe&&t!==`projects`&&(0,k.jsxs)(`div`,{className:Q.grepPanel,children:[(0,k.jsxs)(`div`,{className:Q.grepRow,children:[(0,k.jsx)(`input`,{className:Q.grepInput,value:et,onChange:e=>tt(e.target.value),onKeyDown:e=>e.key===`Enter`&&Kt(),placeholder:`Cerca nel codice...`}),(0,k.jsx)(`button`,{className:Q.grepBtn,onClick:Kt,children:`🔍`}),(0,k.jsx)(`button`,{className:Q.grepClose,onClick:()=>$e(!1),children:`×`})]}),nt.length>0&&(0,k.jsxs)(`div`,{className:Q.grepCount,children:[nt.length,` risultati`]}),(0,k.jsx)(`div`,{className:Q.grepResults,children:nt.length===0?(0,k.jsx)(`div`,{className:Q.grepEmpty,children:`Nessun risultato.`}):nt.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.grepMatch,onClick:()=>qt(e.file),children:[(0,k.jsxs)(`span`,{className:Q.grepMatchFile,children:[e.file,`:`,e.lineNum]}),(0,k.jsx)(`pre`,{className:Q.grepMatchLine,children:e.line})]},t))})]}),it.length>0&&t!==`projects`&&(0,k.jsxs)(`div`,{className:Q.diffPanel,children:[(0,k.jsxs)(`div`,{className:Q.diffHeader,children:[(0,k.jsxs)(`span`,{children:[`🔌 Diff — `,it.length,` file modificati`]}),(0,k.jsx)(`button`,{className:Q.diffClose,onClick:()=>at([]),children:`✕ Chiudi`})]}),it.map((e,t)=>{let n=(e.after||``).split(`
859
859
  `).length-(e.before||``).split(`
860
860
  `).length;return(0,k.jsxs)(`details`,{open:!0,className:Q.diffFile,children:[(0,k.jsxs)(`summary`,{className:Q.diffSummary,children:[(0,k.jsx)(`span`,{className:Q.diffArrow,children:`▲`}),(0,k.jsx)(`span`,{className:Q.diffFileName,children:e.file}),(0,k.jsxs)(`span`,{className:n>=0?Q.diffAdded:Q.diffRemoved,children:[n>=0?`+`:``,n,` linee`]})]}),(0,k.jsx)(`div`,{className:Q.diffContent,children:(0,k.jsx)(uE,{before:e.before,after:e.after})})]},t)})]}),t!==`projects`&&(0,k.jsxs)(`div`,{className:`${Q.chatPanel} ${ut?Q.chatPanelCollapsed:``}`,children:[(0,k.jsxs)(`button`,{className:Q.chatCollapseBtn,onClick:()=>dt(e=>!e),children:[(0,k.jsx)(`span`,{children:ut?`▲`:`▼`}),(0,k.jsx)(`span`,{children:ut?`Show Chat`:`Hide Chat`}),Fe.length>0&&(0,k.jsxs)(`span`,{style:{opacity:.5},children:[`(`,Fe.length,`)`]})]}),(0,k.jsxs)(`div`,{className:Q.chatMessages,ref:Tt,children:[Fe.length===0&&nn&&(0,k.jsxs)(`div`,{className:Q.chatWelcome,children:[`🤖 `,e(`webcraft.doctrine.title`),` — `,(0,k.jsx)(`button`,{className:Q.doctrineOpenBtn,onClick:()=>ue(!0),children:`📖 Open Guide`})]}),Fe.map((e,t)=>(0,k.jsxs)(`div`,{className:e.role===`user`?Q.chatUser:e.role===`system`?Q.chatSystem:Q.chatAgent,children:[e.role===`user`&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`div`,{className:Q.chatUserBubble,children:e.text}),e.attachments&&e.attachments.length>0&&(0,k.jsx)(`div`,{className:Q.chatAttachPreviews,children:e.attachments.map((e,t)=>(0,k.jsxs)(`span`,{className:Q.chatAttachBadge,children:[`📎 `,e.name]},t))})]}),e.role===`system`&&(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`div`,{className:Q.chatSystemBubble,children:e.text}),e.syntaxErrors?.map((e,t)=>(0,k.jsxs)(`div`,{className:Q.chatSyntaxErr,children:[`✕ `,e.file,`: `,e.error]},t))]}),e.role===`agent`&&(()=>{let t=e.text.replace(/<tool>[\s\S]*?<\/tool>/g,``).replace(/<done\s*\/?>/g,``).trim(),n=(e.tools||[]).filter(e=>(e.op===`edit`||e.op===`write`)&&(e.result===`ok`||e.result===`ok_fuzzy`||e.result===`ok_repaired`)),r=(e.tools||[]).filter(e=>e.result?.includes(`not_found`)||e.result?.includes(`error`)||e.result===`blocked_use_edit`),a=t.match(/^(.{10,120}?)[.\n]/),o=a?a[1]+`.`:t.slice(0,120),s=t.length>130;return(0,k.jsxs)(`div`,{className:Q.chatAgentCard,children:[n.map((e,t)=>(0,k.jsxs)(`div`,{style:{margin:`6px 0`,borderRadius:8,overflow:`hidden`,border:`1px solid rgba(255,255,255,0.08)`},children:[(0,k.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,justifyContent:`space-between`,padding:`6px 10px`,background:`rgba(99,102,241,0.1)`,fontSize:11},children:[(0,k.jsxs)(`span`,{style:{fontWeight:600,color:`#818cf8`,cursor:`pointer`},onClick:()=>{let t=g.findIndex(t=>t.name===e.path);t>=0&&(b(t),i(`files`))},children:[`✏ `,e.path]}),(0,k.jsx)(`span`,{style:{color:`#4ade80`,fontSize:10,fontWeight:600},children:e.result===`ok_fuzzy`?`applied (fuzzy)`:e.result===`ok_repaired`?`applied (repaired)`:`✓ applied`})]}),e.oldSnippet||e.newSnippet?(0,k.jsx)(uE,{before:e.oldSnippet||``,after:e.newSnippet||``,contextLines:3}):(0,k.jsx)(`div`,{style:{padding:`6px 10px`,fontSize:11,color:`#4ade80`,background:`rgba(74,222,128,0.05)`},children:`File modified successfully`})]},t)),r.length>0&&(0,k.jsx)(`div`,{style:{margin:`6px 0`,padding:`6px 10px`,background:`rgba(248,113,113,0.08)`,borderRadius:6,fontSize:11,color:`#f87171`},children:r.map((e,t)=>(0,k.jsxs)(`div`,{children:[`❌ `,e.op,` `,e.path,`: `,typeof e.result==`string`?e.result.slice(0,100):``]},t))}),t&&(s?(0,k.jsxs)(`details`,{style:{margin:`6px 0`,fontSize:11},children:[(0,k.jsx)(`summary`,{style:{cursor:`pointer`,color:`var(--dim)`,padding:`4px 0`,userSelect:`none`},children:o.slice(0,100)}),(0,k.jsx)(`div`,{className:Q.chatAgentText,style:{fontSize:11,opacity:.8,marginTop:4},dangerouslySetInnerHTML:{__html:$T(t)}})]}):(0,k.jsx)(`div`,{className:Q.chatAgentText,style:{fontSize:11,opacity:.8},dangerouslySetInnerHTML:{__html:$T(t)}}))]})})()]},t)),ze&&(()=>{let e=Fe[Fe.length-1]?.tools??[],t=e[e.length-1],n=t?t.op===`read`?`Reading ${t.path}`:t.op===`edit`?`Editing ${t.path}`:t.op===`search`?`Searching...`:t.op===`lint`?`Linting ${t.path}`:t.op===`check`?`Checking ${t.path}`:t.op===`run`?`Running command...`:t.op===`sandbox`?`Starting sandbox...`:t.op:`Thinking...`;return(0,k.jsxs)(`div`,{style:{display:`flex`,alignItems:`center`,gap:8,padding:`8px 12px`,fontSize:12,color:`#818cf8`},children:[(0,k.jsx)(`span`,{className:Q.chatAgentRobotAnim,style:{fontSize:14},children:`⟳`}),(0,k.jsx)(`span`,{style:{fontWeight:500},children:n})]})})()]}),Be.length>0&&(0,k.jsx)(`div`,{className:Q.attachPreviews,children:Be.map((e,t)=>(0,k.jsxs)(`span`,{className:Q.attachBadge,children:[`📎 `,e.name,(0,k.jsx)(`button`,{className:Q.removeAttachBtn,onClick:()=>Ve(e=>e.filter((e,n)=>n!==t)),children:`×`})]},t))}),nn?(0,k.jsxs)(`div`,{className:Q.projActiveRow,children:[`📄 `,(0,k.jsx)(`strong`,{className:Q.projActiveName,children:c}),` — scrivi per modificare o migliorare il progetto`]}):(0,k.jsxs)(`div`,{className:Q.projNameRow,children:[(0,k.jsx)(`span`,{className:Q.projNameLabel,children:`Nome progetto:`}),(0,k.jsx)(`input`,{className:Q.projNameInput,value:c,onChange:e=>l(e.target.value),placeholder:`MioProgetto`})]}),(0,k.jsxs)(`div`,{className:Q.chatInputRow,children:[(0,k.jsxs)(`label`,{className:Q.attachLabel,title:`Allega immagine o PDF`,children:[`📎`,(0,k.jsx)(`input`,{ref:Et,type:`file`,multiple:!0,accept:`image/*,.pdf`,style:{display:`none`},onChange:e=>tn(e.target.files)})]}),(0,k.jsx)(`textarea`,{className:Q.chatTextarea,value:Le,onChange:e=>Re(e.target.value),placeholder:nn?`Parla con il tuo agente: chiedi correzioni, migliorie, nuove funzionalità...`:`Descrivi il progetto da creare, poi premi Genera...`,disabled:U,onKeyDown:e=>{e.key===`Enter`&&!e.shiftKey&&(e.preventDefault(),Pt())},rows:4}),(0,k.jsxs)(`div`,{className:Q.chatSendCol,children:[(0,k.jsx)(`button`,{className:Q.chatSendBtn,onClick:()=>Pt(),disabled:U,children:N?`⏳`:nn?`▶`:`▶ Genera`}),U&&!we&&(0,k.jsx)(`button`,{className:Q.chatStopBtn,onClick:Lt,children:`⏹ Stop`})]})]})]}),le&&(0,k.jsx)(`div`,{className:Q.modalOverlay,onClick:()=>ue(!1),children:(0,k.jsxs)(`div`,{className:Q.modal,onClick:e=>e.stopPropagation(),style:{width:720,maxHeight:`90vh`},children:[(0,k.jsxs)(`div`,{className:Q.modalHeader,children:[(0,k.jsxs)(`span`,{className:Q.modalTitle,children:[`📖 `,e(`webcraft.doctrine.title`)]}),(0,k.jsx)(`span`,{className:Q.doctrineSubtitle,children:e(`webcraft.doctrine.subtitle`)}),(0,k.jsx)(`button`,{className:Q.modalClose,onClick:()=>ue(!1),children:`✕`})]}),(0,k.jsx)(`div`,{className:Q.modalBody,style:{gap:0},children:[`phase1`,`phase2`,`phase3`,`phase4`,`phase5`,`tools`,`golden`].map(t=>(0,k.jsxs)(`div`,{className:Q.doctrineSection,children:[(0,k.jsx)(`div`,{className:Q.doctrineSectionTitle,children:e(`webcraft.doctrine.${t}.title`)}),(0,k.jsx)(`div`,{className:Q.doctrineSectionBody,children:e(`webcraft.doctrine.${t}.desc`).split(`
@@ -8,7 +8,7 @@
8
8
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
10
10
  <title>NHA — NotHumanAllowed</title>
11
- <script type="module" crossorigin src="/assets/index-L_v81SPj.js"></script>
11
+ <script type="module" crossorigin src="/assets/index-CnfvvIP7.js"></script>
12
12
  <link rel="stylesheet" crossorigin href="/assets/index-nUBdqB1O.css">
13
13
  </head>
14
14
  <body>