nothumanallowed 13.5.70 → 13.5.72
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 +1 -1
- package/src/commands/ui.mjs +89 -5
- package/src/services/web-ui.mjs +18 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.72",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -4086,7 +4086,11 @@ ${completedHeadings ? `## SECTIONS ALREADY WRITTEN (headings only):\n${completed
|
|
|
4086
4086
|
'Connection': 'keep-alive',
|
|
4087
4087
|
'Access-Control-Allow-Origin': '*',
|
|
4088
4088
|
});
|
|
4089
|
-
const
|
|
4089
|
+
const _sbLogLines = [];
|
|
4090
|
+
const sendLog = (msg) => {
|
|
4091
|
+
_sbLogLines.push(msg);
|
|
4092
|
+
res.write(`data: ${JSON.stringify({ type: 'log', msg })}\n\n`);
|
|
4093
|
+
};
|
|
4090
4094
|
const sendReady = (port, dir) => res.write(`data: ${JSON.stringify({ type: 'ready', port, dir })}\n\n`);
|
|
4091
4095
|
const sendError = (msg) => res.write(`data: ${JSON.stringify({ type: 'error', msg })}\n\n`);
|
|
4092
4096
|
|
|
@@ -4274,6 +4278,32 @@ module.exports = { get, set, del, exists };
|
|
|
4274
4278
|
fs.mkdirSync(path.join(sandboxDir, 'server', 'services'), { recursive: true });
|
|
4275
4279
|
fs.writeFileSync(path.join(sandboxDir, 'server', 'services', 'cache.js'), cacheShim, 'utf8');
|
|
4276
4280
|
|
|
4281
|
+
// Models shim — LLM often generates require('../models/User') etc. that don't exist
|
|
4282
|
+
// Create a generic User model shim backed by the in-memory DB shim
|
|
4283
|
+
const userModelShim = `
|
|
4284
|
+
// NHA WebCraft Sandbox — models/User shim (in-memory, no PostgreSQL)
|
|
4285
|
+
const db = require('../db');
|
|
4286
|
+
const bcrypt = require('bcryptjs');
|
|
4287
|
+
const crypto = require('crypto');
|
|
4288
|
+
const User = {
|
|
4289
|
+
findById: async function(id) { var r = await db.query('SELECT * FROM users WHERE id=$1',[id]); return (r.rows||[])[0]||null; },
|
|
4290
|
+
findByEmail: async function(email) { var r = await db.query('SELECT * FROM users WHERE email=$1',[email]); return (r.rows||[])[0]||null; },
|
|
4291
|
+
create: async function(data) {
|
|
4292
|
+
var hash = data.password ? await bcrypt.hash(data.password,12) : data.password_hash||'';
|
|
4293
|
+
var token = crypto.randomBytes(32).toString('hex');
|
|
4294
|
+
var r = await db.query('INSERT INTO users (name,email,password_hash,verification_token) VALUES ($1,$2,$3,$4) RETURNING *',[data.name||data.username||'',data.email,hash,token]);
|
|
4295
|
+
return (r.rows||[])[0]||null;
|
|
4296
|
+
},
|
|
4297
|
+
update: async function(id, data) { var r = await db.query('UPDATE users SET verified=true WHERE id=$1 RETURNING *',[id]); return (r.rows||[])[0]||null; },
|
|
4298
|
+
findByVerificationToken: async function(token) { var r = await db.query('SELECT * FROM users WHERE verification_token=$1',[token]); return (r.rows||[])[0]||null; },
|
|
4299
|
+
};
|
|
4300
|
+
module.exports = User;
|
|
4301
|
+
`;
|
|
4302
|
+
fs.mkdirSync(path.join(sandboxDir, 'server', 'models'), { recursive: true });
|
|
4303
|
+
if (!fs.existsSync(path.join(sandboxDir, 'server', 'models', 'User.js'))) {
|
|
4304
|
+
fs.writeFileSync(path.join(sandboxDir, 'server', 'models', 'User.js'), userModelShim, 'utf8');
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4277
4307
|
// Validators shim — LLM often generates require('../utils/validators') with helpers that don't exist
|
|
4278
4308
|
const validatorsShim = `
|
|
4279
4309
|
// NHA WebCraft Sandbox — utils/validators shim
|
|
@@ -4310,6 +4340,19 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
|
|
|
4310
4340
|
[/require\(['"]compression['"]\)/g, "(()=>(r,s,n)=>n())"],
|
|
4311
4341
|
[/require\(['"]express-validator['"]\)/g, "{body:()=>({isEmail:()=>({normalizeEmail:()=>({run:async()=>{}})}),isLength:()=>({run:async()=>{}}),trim:()=>({escape:()=>({run:async()=>{}})}),notEmpty:()=>({run:async()=>{}})}),validationResult:()=>({isEmpty:()=>true,array:()=>[]})}"],
|
|
4312
4342
|
[/require\(['"]validator['"]\)/g, "{isEmail:(s)=>/^[^@\\s]+@[^@\\s]+[.][^@\\s]+$/.test(s),escape:(s)=>String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'),trim:(s)=>String(s).trim(),isEmpty:(s)=>!s||!String(s).trim(),isLength:(s,o)=>{ var l=String(s).length; return (!o.min||l>=o.min)&&(!o.max||l<=o.max); }}"],
|
|
4343
|
+
[/require\(['"]handlebars['"]\)/g, "{compile:(t)=>(d)=>t.replace(/\\{\\{([^}]+)\\}\\}/g,(_,k)=>d[k.trim()]||''),registerHelper:()=>{},registerPartial:()=>{}}"],
|
|
4344
|
+
[/require\(['"]express-handlebars['"]\)/g, "{engine:()=>(p,o,cb)=>cb(null,'<html>'+JSON.stringify(o)+'</html>')}"],
|
|
4345
|
+
[/require\(['"]hbs['"]\)/g, "{registerHelper:()=>{},registerPartial:()=>{}}"],
|
|
4346
|
+
[/require\(['"]ejs['"]\)/g, "{render:(t,d)=>t.replace(/<%=([^%]+)%>/g,(_,k)=>d[k.trim()]||''),renderFile:(f,d,o,cb)=>{ if(typeof o==='function'){o(null,'');}else if(cb){cb(null,'');} }}"],
|
|
4347
|
+
[/require\(['"]pug['"]\)/g, "{compile:()=>(d)=>'',renderFile:(f,d,cb)=>cb&&cb(null,'')}"],
|
|
4348
|
+
[/require\(['"]nunjucks['"]\)/g, "{configure:()=>({}),render:(t,d)=>JSON.stringify(d),renderString:(t,d)=>JSON.stringify(d)}"],
|
|
4349
|
+
[/require\(['"]mustache['"]\)/g, "{render:(t,d)=>t.replace(/\\{\\{([^}]+)\\}\\}/g,(_,k)=>d[k.trim()]||'')}"],
|
|
4350
|
+
[/require\(['"]marked['"]\)/g, "{marked:(s)=>s,parse:(s)=>s}"],
|
|
4351
|
+
[/require\(['"]highlight\.js['"]\)/g, "{highlight:(c)=>({value:c}),highlightAuto:(c)=>({value:c})}"],
|
|
4352
|
+
[/require\(['"]nodemailer['"]\)/g, "{createTransport:()=>({sendMail:(o,cb)=>{ if(cb)cb(null,{messageId:'sandbox-'+Date.now()}); return Promise.resolve({messageId:'sandbox'}); }})}"],
|
|
4353
|
+
[/require\(['"]stripe['"]\)/g, "(()=>({customers:{create:async()=>({id:'cus_sandbox'})},paymentIntents:{create:async()=>({id:'pi_sandbox',client_secret:'sandbox_secret'})}}))()"],
|
|
4354
|
+
[/require\(['"]@sendgrid\/mail['"]\)/g, "{setApiKey:()=>{},send:async()=>({statusCode:202})}"],
|
|
4355
|
+
[/require\(['"]twilio['"]\)/g, "(()=>({messages:{create:async()=>({sid:'SM_sandbox'})}}))()"],
|
|
4313
4356
|
[/require\(['"]cookie-parser['"]\)/g, "(()=>(r,s,n)=>{r.cookies=r.cookies||{};n()})"],
|
|
4314
4357
|
[/require\(['"]passport['"]\)/g, "{initialize:()=>(r,s,n)=>n(),session:()=>(r,s,n)=>n(),authenticate:()=>(r,s,n)=>n&&n()}"],
|
|
4315
4358
|
[/require\(['"]express-session['"]\)/g, "(()=>(r,s,n)=>{r.session=r.session||{};n()})"],
|
|
@@ -4330,6 +4373,10 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
|
|
|
4330
4373
|
[/require\(['"]\.\.\/\.\.\/config['"]\)/g, "{env:process.env}"],
|
|
4331
4374
|
[/require\(['"]\.\.\/config['"]\)/g, "{env:process.env}"],
|
|
4332
4375
|
[/require\(['"]\.\/config['"]\)/g, "{env:process.env}"],
|
|
4376
|
+
// models/* — redirect to shims in server/models/
|
|
4377
|
+
[/require\(['"]\.\.\/models\/User['"]\)/g, "require('../models/User')"],
|
|
4378
|
+
[/require\(['"]\.\/models\/User['"]\)/g, "require('./models/User')"],
|
|
4379
|
+
[/require\(['"]\.\.\/models\/user['"]\)/g, "require('../models/User')"],
|
|
4333
4380
|
// utils/* — LLM generates helpers that don't exist; redirect to shim
|
|
4334
4381
|
[/require\(['"]\.\.\/utils\/validators['"]\)/g, "require('../utils/validators')"],
|
|
4335
4382
|
[/require\(['"]\.\/utils\/validators['"]\)/g, "require('./utils/validators')"],
|
|
@@ -4493,8 +4540,44 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
|
|
|
4493
4540
|
});
|
|
4494
4541
|
|
|
4495
4542
|
sendLog(`✅ Sandbox pronta!`);
|
|
4543
|
+
|
|
4544
|
+
// Write sandbox log to skills/ so the agent can read it as context
|
|
4545
|
+
try {
|
|
4546
|
+
const _nl = '\n';
|
|
4547
|
+
const logTs = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
4548
|
+
const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '.log';
|
|
4549
|
+
const logsDir = path.join(sandboxDir, 'skills');
|
|
4550
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
4551
|
+
// Keep only last 3 log files to avoid bloat
|
|
4552
|
+
const existing = fs.readdirSync(logsDir).filter(f => f.endsWith('.log') && f.startsWith(projName + '-'));
|
|
4553
|
+
existing.sort().slice(0, -2).forEach(f => { try { fs.unlinkSync(path.join(logsDir, f)); } catch(_) {} });
|
|
4554
|
+
const logContent = '# Sandbox Log — ' + projName + _nl + 'Avviato: ' + logTs + _nl + 'Porta: ' + freePort + _nl + _nl + _sbLogLines.join(_nl);
|
|
4555
|
+
fs.writeFileSync(path.join(logsDir, logName), logContent, 'utf8');
|
|
4556
|
+
// Also update _index.json so the UI knows the type
|
|
4557
|
+
const idxPath = path.join(logsDir, '_index.json');
|
|
4558
|
+
let idx = {};
|
|
4559
|
+
try { idx = JSON.parse(fs.readFileSync(idxPath, 'utf8')); } catch(_) {}
|
|
4560
|
+
idx[logName] = 'log';
|
|
4561
|
+
fs.writeFileSync(idxPath, JSON.stringify(idx), 'utf8');
|
|
4562
|
+
} catch(_) {}
|
|
4563
|
+
|
|
4496
4564
|
sendReady(freePort, sandboxDir);
|
|
4497
4565
|
} catch (e) {
|
|
4566
|
+
// Write error log too
|
|
4567
|
+
try {
|
|
4568
|
+
const _nl = '\n';
|
|
4569
|
+
const logTs = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
4570
|
+
const logName = projName + '-' + logTs.replace(/[: ]/g, '-') + '-ERROR.log';
|
|
4571
|
+
const logsDir = path.join(sandboxDir, 'skills');
|
|
4572
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
4573
|
+
const logContent = '# Sandbox Log — ' + projName + ' [ERRORE]' + _nl + 'Avviato: ' + logTs + _nl + _nl + _sbLogLines.join(_nl) + _nl + _nl + '❌ ERRORE: ' + e.message;
|
|
4574
|
+
fs.writeFileSync(path.join(logsDir, logName), logContent, 'utf8');
|
|
4575
|
+
const idxPath = path.join(logsDir, '_index.json');
|
|
4576
|
+
let idx = {};
|
|
4577
|
+
try { idx = JSON.parse(fs.readFileSync(idxPath, 'utf8')); } catch(_) {}
|
|
4578
|
+
idx[logName] = 'log';
|
|
4579
|
+
fs.writeFileSync(idxPath, JSON.stringify(idx), 'utf8');
|
|
4580
|
+
} catch(_) {}
|
|
4498
4581
|
sendError(e.message);
|
|
4499
4582
|
}
|
|
4500
4583
|
res.end();
|
|
@@ -4536,14 +4619,14 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
|
|
|
4536
4619
|
return;
|
|
4537
4620
|
}
|
|
4538
4621
|
|
|
4539
|
-
// Rate-limit Liara:
|
|
4622
|
+
// Rate-limit Liara: 6 per 5 minutes (autofix needs headroom for multi-crash startup sequences)
|
|
4540
4623
|
const isLiara = !config.llm || !config.llm.apiKey || config.llm.provider === 'nha';
|
|
4541
4624
|
if (isLiara) {
|
|
4542
4625
|
if (!global._wcAgentCallLog) global._wcAgentCallLog = [];
|
|
4543
4626
|
const fiveMinAgo = Date.now() - 5 * 60 * 1000;
|
|
4544
4627
|
global._wcAgentCallLog = global._wcAgentCallLog.filter(t => t > fiveMinAgo);
|
|
4545
|
-
if (global._wcAgentCallLog.length >=
|
|
4546
|
-
sendJSON(res, 429, { error: 'Auto-fix rate limit: massimo
|
|
4628
|
+
if (global._wcAgentCallLog.length >= 6) {
|
|
4629
|
+
sendJSON(res, 429, { error: 'Auto-fix rate limit: massimo 6 correzioni ogni 5 minuti con Liara. Usa una tua API key per correzioni illimitate.' });
|
|
4547
4630
|
logRequest(method, pathname, 429, Date.now() - start);
|
|
4548
4631
|
return;
|
|
4549
4632
|
}
|
|
@@ -4573,7 +4656,7 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
|
|
|
4573
4656
|
let typeIndex = {};
|
|
4574
4657
|
if (fs.existsSync(indexPath)) { try { typeIndex = JSON.parse(fs.readFileSync(indexPath, 'utf8')); } catch(_) {} }
|
|
4575
4658
|
const defaultType = (n) => n === 'memory.md' ? 'memory' : n === 'liara.md' ? 'provider' : 'skill';
|
|
4576
|
-
const sections = { memory: [], provider: [], skill: [] };
|
|
4659
|
+
const sections = { memory: [], provider: [], skill: [], log: [] };
|
|
4577
4660
|
for (const fname of fs.readdirSync(skillsDir)) {
|
|
4578
4661
|
if (!fname.endsWith('.md')) continue;
|
|
4579
4662
|
try {
|
|
@@ -4588,6 +4671,7 @@ module.exports = { validateEmail, sanitizeText, validatePassword, validateUserna
|
|
|
4588
4671
|
if (sections.memory.length) parts.push('MEMORIA PROGETTO:\n' + sections.memory.join('\n\n'));
|
|
4589
4672
|
if (sections.provider.length) parts.push('ISTRUZIONI MODELLO AI:\n' + sections.provider.join('\n\n'));
|
|
4590
4673
|
if (sections.skill.length) parts.push('SKILLS & PATTERN:\n' + sections.skill.join('\n\n'));
|
|
4674
|
+
if (sections.log && sections.log.length) parts.push('LOG AVVIO SANDBOX (ultimo):\n' + sections.log[sections.log.length - 1]);
|
|
4591
4675
|
if (parts.length) skillsContext = '\n' + parts.join('\n\n') + '\n';
|
|
4592
4676
|
}
|
|
4593
4677
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -6565,10 +6565,10 @@ function wcEsc(s){return s?String(s).replace(/&/g,'&').replace(/</g,'<').
|
|
|
6565
6565
|
|
|
6566
6566
|
function renderWebCraft(el) {
|
|
6567
6567
|
var fileTabsHtml = wcState.generatedFiles.length > 0
|
|
6568
|
-
? '<div style="display:flex;gap:0;overflow-x:auto;border-bottom:1px solid var(--border);margin-bottom:0;flex-shrink:0">' +
|
|
6568
|
+
? '<div id="wcFileTabsRow" style="display:flex;gap:0;overflow-x:auto;border-bottom:1px solid var(--border);margin-bottom:0;flex-shrink:0;scrollbar-width:none">' +
|
|
6569
6569
|
wcState.generatedFiles.map(function(f,i){
|
|
6570
6570
|
var active = i === wcState.activeFile;
|
|
6571
|
-
return '<button onclick="wcSetFile('+i+')" style="padding:6px 14px;font-size:11px;font-family:var(--mono);font-weight:'+(active?'700':'400')+';background:'+(active?'var(--bg3)':'transparent')+';border:none;border-bottom:2px solid '+(active?'var(--green3)':'transparent')+';color:'+(active?'var(--green)':'var(--dim)')+';cursor:pointer;white-space:nowrap;flex-shrink:0">'+wcEsc(f.name)+'</button>';
|
|
6571
|
+
return '<button id="wcTab'+i+'" onclick="wcSetFile('+i+')" style="padding:6px 14px;font-size:11px;font-family:var(--mono);font-weight:'+(active?'700':'400')+';background:'+(active?'var(--bg3)':'transparent')+';border:none;border-bottom:2px solid '+(active?'var(--green3)':'transparent')+';color:'+(active?'var(--green)':'var(--dim)')+';cursor:pointer;white-space:nowrap;flex-shrink:0">'+wcEsc(f.name)+'</button>';
|
|
6572
6572
|
}).join('') +
|
|
6573
6573
|
'</div>'
|
|
6574
6574
|
: '';
|
|
@@ -6727,11 +6727,11 @@ function wcOpenSandbox() { if (wcState.sandbox.port) window.open('http://127.0.0
|
|
|
6727
6727
|
// ── WebCraft Context Files (Skills / Memory / Provider) ───────────────────────
|
|
6728
6728
|
|
|
6729
6729
|
function wcFileTypeIcon(type) {
|
|
6730
|
-
return type === 'memory' ? '🧠' : type === 'provider' ? '🤖' : '📋';
|
|
6730
|
+
return type === 'memory' ? '🧠' : type === 'provider' ? '🤖' : type === 'log' ? '📄' : '📋';
|
|
6731
6731
|
}
|
|
6732
6732
|
function wcFileTypeBadge(type) {
|
|
6733
|
-
var colors = { memory: '#7c5cbf', provider: '#2a7fff', skill: '#1a7a4a' };
|
|
6734
|
-
var labels = { memory: 'memory', provider: 'provider', skill: 'skill' };
|
|
6733
|
+
var colors = { memory: '#7c5cbf', provider: '#2a7fff', skill: '#1a7a4a', log: '#555' };
|
|
6734
|
+
var labels = { memory: 'memory', provider: 'provider', skill: 'skill', log: 'log' };
|
|
6735
6735
|
return '<span style="font-size:9px;padding:1px 5px;border-radius:3px;background:' + (colors[type]||'#444') + ';color:#fff;margin-left:4px;flex-shrink:0">' + (labels[type]||type) + '</span>';
|
|
6736
6736
|
}
|
|
6737
6737
|
|
|
@@ -6756,14 +6756,16 @@ function wcSkillsPanelHtml() {
|
|
|
6756
6756
|
// Can add new skill only (memory + provider are singletons already in defaults)
|
|
6757
6757
|
var rows = wcSkills.map(function(s, si) {
|
|
6758
6758
|
var isSingleton = s.type === 'memory' || s.type === 'provider';
|
|
6759
|
+
var isLog = s.type === 'log';
|
|
6759
6760
|
var isEmpty = !s.content || s.content.trim() === '';
|
|
6760
6761
|
return '<div style="display:flex;align-items:center;gap:4px;padding:5px 0;border-bottom:1px solid var(--border)">' +
|
|
6761
6762
|
'<span style="font-size:13px;flex-shrink:0">' + wcFileTypeIcon(s.type) + '</span>' +
|
|
6762
|
-
'<span style="font-size:11px;color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + wcEsc(s.name) + '</span>' +
|
|
6763
|
+
'<span style="font-size:11px;color:'+(isLog?'var(--dim)':'var(--text)')+';flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="'+wcEsc(s.name)+'">' + wcEsc(s.name) + '</span>' +
|
|
6763
6764
|
wcFileTypeBadge(s.type) +
|
|
6764
|
-
(isEmpty ? '<span title="Vuoto" style="font-size:9px;color:#e09020;flex-shrink:0">⚠</span>' : '') +
|
|
6765
|
-
'<button onclick="wcOpenSkill('+si+')" title="Modifica" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px;flex-shrink:0"
|
|
6766
|
-
(!isSingleton ? '<button onclick="wcClearSkill('+si+')" title="Svuota" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:2px 4px;flex-shrink:0">🗑</button>' : '') +
|
|
6765
|
+
(!isLog && isEmpty ? '<span title="Vuoto" style="font-size:9px;color:#e09020;flex-shrink:0">⚠</span>' : '') +
|
|
6766
|
+
'<button onclick="wcOpenSkill('+si+')" title="'+(isLog?'Visualizza log':'Modifica')+'" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:12px;padding:2px 4px;flex-shrink:0">'+(isLog?'👁':'✎')+'</button>' +
|
|
6767
|
+
(!isSingleton && !isLog ? '<button onclick="wcClearSkill('+si+')" title="Svuota" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:2px 4px;flex-shrink:0">🗑</button>' : '') +
|
|
6768
|
+
(isLog ? '<button onclick="wcDeleteSkill('+si+')" title="Elimina log" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:11px;padding:2px 4px;flex-shrink:0">🗑</button>' : '') +
|
|
6767
6769
|
'</div>';
|
|
6768
6770
|
}).join('');
|
|
6769
6771
|
return '<div style="background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:14px">' +
|
|
@@ -7493,7 +7495,13 @@ function wcAddField() {
|
|
|
7493
7495
|
wcState.authFields.push({name:'field'+wcState.authFields.length,label:'New field',type:'text',required:false});
|
|
7494
7496
|
renderWebCraft(document.getElementById('content'));
|
|
7495
7497
|
}
|
|
7496
|
-
function wcSetFile(i) {
|
|
7498
|
+
function wcSetFile(i) {
|
|
7499
|
+
wcState.activeFile = i;
|
|
7500
|
+
renderWebCraft(document.getElementById('content'));
|
|
7501
|
+
// Scroll active tab into view after render
|
|
7502
|
+
var tab = document.getElementById('wcTab' + i);
|
|
7503
|
+
if (tab) tab.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
|
|
7504
|
+
}
|
|
7497
7505
|
|
|
7498
7506
|
// ── WebCraft: Diff Viewer ─────────────────────────────────────────────────────
|
|
7499
7507
|
function wcDiffLines(before, after) {
|