tina4-nodejs 3.13.149 → 3.13.150
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/CLAUDE.md +2 -2
- package/package.json +1 -1
- package/packages/cli/dist/bin.js +29 -3
- package/packages/core/dist/index.js +29 -3
- package/packages/core/public/bundled-component-provenance.json +2 -2
- package/packages/core/public/js/tina4-dev-admin-split.html +202 -0
- package/packages/core/public/js/tina4-dev-admin.min.js +147 -138
- package/packages/core/src/devAdmin.ts +33 -0
- package/packages/core/src/metrics.ts +5 -1
- package/packages/core/src/plan.ts +1 -1
- package/packages/orm/dist/index.js +29 -3
package/CLAUDE.md
CHANGED
|
@@ -13,13 +13,13 @@ Even if the skill text is not currently loaded, these are non-negotiable:
|
|
|
13
13
|
|
|
14
14
|
The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this block is the always-on floor.
|
|
15
15
|
|
|
16
|
-
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.
|
|
16
|
+
# CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.150)
|
|
17
17
|
|
|
18
18
|
> This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
|
|
19
19
|
|
|
20
20
|
## What This Project Is
|
|
21
21
|
|
|
22
|
-
Tina4 for Node.js/TypeScript v3.13.
|
|
22
|
+
Tina4 for Node.js/TypeScript v3.13.150 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
|
|
23
23
|
|
|
24
24
|
The philosophy: zero ceremony, batteries included, file system as source of truth.
|
|
25
25
|
|
package/package.json
CHANGED
package/packages/cli/dist/bin.js
CHANGED
|
@@ -26381,7 +26381,7 @@ function enginePath() {
|
|
|
26381
26381
|
function runEngine(target) {
|
|
26382
26382
|
const binary = enginePath();
|
|
26383
26383
|
if (!binary) throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
|
|
26384
|
-
const processResult = spawnSync(binary, ["metrics", "--path", target, "--json"], {
|
|
26384
|
+
const processResult = spawnSync(binary, ["metrics", "--path", target, "--json", "--no-history"], {
|
|
26385
26385
|
encoding: "utf8",
|
|
26386
26386
|
timeout: 6e4,
|
|
26387
26387
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -35582,7 +35582,7 @@ var init_plan = __esm({
|
|
|
35582
35582
|
const existing = (currentPlan.steps || []).map((s) => s.text || "");
|
|
35583
35583
|
const title = currentPlan.title || target;
|
|
35584
35584
|
const goal = currentPlan.goal || "";
|
|
35585
|
-
const aiUrl = process.env.TINA4_AI_URL || "http://localhost:
|
|
35585
|
+
const aiUrl = process.env.TINA4_AI_URL || "http://localhost:11435/api/chat";
|
|
35586
35586
|
const aiModel = process.env.TINA4_AI_MODEL || "qwen2.5-coder:14b";
|
|
35587
35587
|
const prompts = fleshPrompt(title, goal, existing, prompt);
|
|
35588
35588
|
const fetched = await fetchFleshReply(aiUrl, aiModel, prompts);
|
|
@@ -38019,7 +38019,7 @@ function toolbarJs() {
|
|
|
38019
38019
|
connect();
|
|
38020
38020
|
})();`;
|
|
38021
38021
|
}
|
|
38022
|
-
var cpuCount, DEV_MCP_PREFIXES, DEV_PUBLIC_ASSETS, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleToolbarCss, handleToolbarJs;
|
|
38022
|
+
var cpuCount, DEV_MCP_PREFIXES, DEV_PUBLIC_ASSETS, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleDevAdminSplit, handleToolbarCss, handleToolbarJs;
|
|
38023
38023
|
var init_devAdmin = __esm({
|
|
38024
38024
|
"../core/src/devAdmin.ts"() {
|
|
38025
38025
|
"use strict";
|
|
@@ -38489,6 +38489,7 @@ var init_devAdmin = __esm({
|
|
|
38489
38489
|
{ method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
|
|
38490
38490
|
// JS asset
|
|
38491
38491
|
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
|
|
38492
|
+
{ method: "GET", pattern: "/__dev/split", handler: handleDevAdminSplit },
|
|
38492
38493
|
// Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
|
|
38493
38494
|
// CSP-clean (no inline style=, onclick=, or <script>) under a strict
|
|
38494
38495
|
// `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
|
|
@@ -39927,6 +39928,31 @@ var init_devAdmin = __esm({
|
|
|
39927
39928
|
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
39928
39929
|
res.raw.end("tina4-dev-admin.min.js not found");
|
|
39929
39930
|
};
|
|
39931
|
+
handleDevAdminSplit = async (_req, res) => {
|
|
39932
|
+
const { readFileSync: readFileSync30, existsSync: existsSync38 } = await import("node:fs");
|
|
39933
|
+
const { dirname: dirname20, join: join41, resolve: resolve29 } = await import("node:path");
|
|
39934
|
+
const { fileURLToPath: fileURLToPath12 } = await import("node:url");
|
|
39935
|
+
const dir = dirname20(fileURLToPath12(import.meta.url));
|
|
39936
|
+
const candidates = [
|
|
39937
|
+
join41(dir, "..", "public", "js", "tina4-dev-admin-split.html"),
|
|
39938
|
+
join41(dir, "..", "..", "public", "js", "tina4-dev-admin-split.html"),
|
|
39939
|
+
resolve29(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin-split.html"),
|
|
39940
|
+
resolve29(process.cwd(), "public", "js", "tina4-dev-admin-split.html")
|
|
39941
|
+
];
|
|
39942
|
+
for (const path10 of candidates) {
|
|
39943
|
+
if (existsSync38(path10)) {
|
|
39944
|
+
try {
|
|
39945
|
+
const content = readFileSync30(path10, "utf-8");
|
|
39946
|
+
res.raw.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
|
|
39947
|
+
res.raw.end(content);
|
|
39948
|
+
return;
|
|
39949
|
+
} catch {
|
|
39950
|
+
}
|
|
39951
|
+
}
|
|
39952
|
+
}
|
|
39953
|
+
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
39954
|
+
res.raw.end("tina4-dev-admin-split.html not found");
|
|
39955
|
+
};
|
|
39930
39956
|
handleToolbarCss = (_req, res) => {
|
|
39931
39957
|
res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
|
|
39932
39958
|
res.raw.end(toolbarCss());
|
|
@@ -26360,7 +26360,7 @@ function enginePath() {
|
|
|
26360
26360
|
function runEngine(target) {
|
|
26361
26361
|
const binary = enginePath();
|
|
26362
26362
|
if (!binary) throw new MetricsEngineError(`tina4 not found on PATH - ${INSTALL_HINT}`);
|
|
26363
|
-
const processResult = spawnSync(binary, ["metrics", "--path", target, "--json"], {
|
|
26363
|
+
const processResult = spawnSync(binary, ["metrics", "--path", target, "--json", "--no-history"], {
|
|
26364
26364
|
encoding: "utf8",
|
|
26365
26365
|
timeout: 6e4,
|
|
26366
26366
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -35538,7 +35538,7 @@ var init_plan = __esm({
|
|
|
35538
35538
|
const existing = (currentPlan.steps || []).map((s) => s.text || "");
|
|
35539
35539
|
const title = currentPlan.title || target;
|
|
35540
35540
|
const goal = currentPlan.goal || "";
|
|
35541
|
-
const aiUrl = process.env.TINA4_AI_URL || "http://localhost:
|
|
35541
|
+
const aiUrl = process.env.TINA4_AI_URL || "http://localhost:11435/api/chat";
|
|
35542
35542
|
const aiModel = process.env.TINA4_AI_MODEL || "qwen2.5-coder:14b";
|
|
35543
35543
|
const prompts = fleshPrompt(title, goal, existing, prompt);
|
|
35544
35544
|
const fetched = await fetchFleshReply(aiUrl, aiModel, prompts);
|
|
@@ -37975,7 +37975,7 @@ function toolbarJs() {
|
|
|
37975
37975
|
connect();
|
|
37976
37976
|
})();`;
|
|
37977
37977
|
}
|
|
37978
|
-
var cpuCount, DEV_MCP_PREFIXES, DEV_PUBLIC_ASSETS, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleToolbarCss, handleToolbarJs;
|
|
37978
|
+
var cpuCount, DEV_MCP_PREFIXES, DEV_PUBLIC_ASSETS, DEV_SECRET_BASENAMES, DEV_SECRET_SUFFIXES, MessageLog, RequestInspector, ErrorTracker, DevMailboxStore, DevQueue, WsTracker, DevAdmin, handleDashboard, _reloadMtime, _reloadFile, handleMtime, handleReload, handleMessages, handleMessagesClear, handleRequests, handleRequestsClear, handleSystem, handleMessagesSearch, handleQueue, handleQueueTopics, handleQueueDeadLetters, handleQueueRetry, handleQueuePurge, handleQueueReplay, handleMailbox, handleMailboxRead, handleMailboxSeed, handleMailboxClear, handleTable, handleTables, handleSeed, handleQuery, handleBroken, handleBrokenResolve, handleBrokenClear, handleWebsockets, handleWebsocketsDisconnect, handleTool, DEFAULT_MCP_URL, handleGroundingStatus, handleGroundingToken, handleMigrate, handleSeedRun, handleTest, handleThreads, handleThreadsSub, handleConnections, handleConnectionsTest, handleConnectionsSave, __devAdminFilename, __devAdminDirname, handleGalleryList, handleVersionCheck, handleThoughts, handleSuperviseStub, DEV_FILES_IGNORED, handleFiles, DEV_ADMIN_LANG_MAP, handleFileRead, handleFileSave, handleFileRaw, handleFileRename, handleFileDelete, handleDepsSearch, handleDepsInstall, handleGitStatus, handleMcpTools, handleMcpCall, handleMcpStreamable, handleMcpDelete, handleMcpGet405, handleMcpLegacyMessage, handleMcpSse, handleScaffoldList, handleScaffoldRun, handlePlanCurrent, handlePlanList, handlePlanCreate, handlePlanSwitch, handlePlanCompleteStep, handlePlanAddStep, handlePlanNote, handlePlanArchive, handlePlanRead, handlePlanFlesh, handleIndexRebuild, handleIndexSearch, handleIndexFile, handleIndexOverview, handleDocsSearch, handleDocsClass, handleDocsMethod, handleDocsIndex, handleDocsWellKnown, handleDevAdminJs, handleDevAdminSplit, handleToolbarCss, handleToolbarJs;
|
|
37979
37979
|
var init_devAdmin = __esm({
|
|
37980
37980
|
"src/devAdmin.ts"() {
|
|
37981
37981
|
"use strict";
|
|
@@ -38445,6 +38445,7 @@ var init_devAdmin = __esm({
|
|
|
38445
38445
|
{ method: "GET", pattern: "/__dev/api/docs/.well-known.json", handler: handleDocsWellKnown },
|
|
38446
38446
|
// JS asset
|
|
38447
38447
|
{ method: "GET", pattern: "/__dev/js/tina4-dev-admin.min.js", handler: handleDevAdminJs },
|
|
38448
|
+
{ method: "GET", pattern: "/__dev/split", handler: handleDevAdminSplit },
|
|
38448
38449
|
// Dev-toolbar assets — served as external CSS/JS so the injected toolbar is
|
|
38449
38450
|
// CSP-clean (no inline style=, onclick=, or <script>) under a strict
|
|
38450
38451
|
// `default-src 'self'`. Parity with PHP's /__dev/toolbar.css + toolbar.js.
|
|
@@ -39883,6 +39884,31 @@ var init_devAdmin = __esm({
|
|
|
39883
39884
|
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
39884
39885
|
res.raw.end("tina4-dev-admin.min.js not found");
|
|
39885
39886
|
};
|
|
39887
|
+
handleDevAdminSplit = async (_req, res) => {
|
|
39888
|
+
const { readFileSync: readFileSync27, existsSync: existsSync29 } = await import("node:fs");
|
|
39889
|
+
const { dirname: dirname17, join: join33, resolve: resolve21 } = await import("node:path");
|
|
39890
|
+
const { fileURLToPath: fileURLToPath10 } = await import("node:url");
|
|
39891
|
+
const dir = dirname17(fileURLToPath10(import.meta.url));
|
|
39892
|
+
const candidates = [
|
|
39893
|
+
join33(dir, "..", "public", "js", "tina4-dev-admin-split.html"),
|
|
39894
|
+
join33(dir, "..", "..", "public", "js", "tina4-dev-admin-split.html"),
|
|
39895
|
+
resolve21(process.cwd(), "node_modules", "tina4-nodejs", "packages", "core", "public", "js", "tina4-dev-admin-split.html"),
|
|
39896
|
+
resolve21(process.cwd(), "public", "js", "tina4-dev-admin-split.html")
|
|
39897
|
+
];
|
|
39898
|
+
for (const path10 of candidates) {
|
|
39899
|
+
if (existsSync29(path10)) {
|
|
39900
|
+
try {
|
|
39901
|
+
const content = readFileSync27(path10, "utf-8");
|
|
39902
|
+
res.raw.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-cache" });
|
|
39903
|
+
res.raw.end(content);
|
|
39904
|
+
return;
|
|
39905
|
+
} catch {
|
|
39906
|
+
}
|
|
39907
|
+
}
|
|
39908
|
+
}
|
|
39909
|
+
res.raw.writeHead(404, { "Content-Type": "text/plain" });
|
|
39910
|
+
res.raw.end("tina4-dev-admin-split.html not found");
|
|
39911
|
+
};
|
|
39886
39912
|
handleToolbarCss = (_req, res) => {
|
|
39887
39913
|
res.raw.writeHead(200, { "Content-Type": "text/css; charset=utf-8", "Cache-Control": "no-cache" });
|
|
39888
39914
|
res.raw.end(toolbarCss());
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"path": "js/tina4-dev-admin.min.js",
|
|
44
|
-
"component": "tina4-dev-admin-
|
|
45
|
-
"sha256": "
|
|
44
|
+
"component": "tina4-dev-admin-8c4617d",
|
|
45
|
+
"sha256": "6b05be06b5d76ee4126a11f019d104d3920c1e3234250cfe1966a33f34c1a234"
|
|
46
46
|
}
|
|
47
47
|
]
|
|
48
48
|
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>Tina4 Dev - Split</title>
|
|
3
|
+
<style>
|
|
4
|
+
html,body{margin:0;height:100%;background:#0f172a;font-family:system-ui,-apple-system,sans-serif;overflow:hidden}
|
|
5
|
+
/* One top toolbar controls both panels and the preview device. */
|
|
6
|
+
.t4-topbar{height:40px;box-sizing:border-box;display:flex;align-items:center;gap:6px;padding:0 10px;background:#0b1220;border-bottom:1px solid #1f2937}
|
|
7
|
+
.t4-brand{color:#e2e8f0;font:600 12px system-ui;margin-right:6px}
|
|
8
|
+
.t4-spacer{flex:1 1 auto}
|
|
9
|
+
.t4-devbtn{background:#1e293b;color:#cbd5e1;border:1px solid #334155;border-radius:6px;padding:3px 9px;font:12px system-ui;cursor:pointer}
|
|
10
|
+
.t4-devbtn.active{background:#2563eb;border-color:#2563eb;color:#fff}
|
|
11
|
+
/* Icon toggle buttons: full opacity + highlight when the panel is shown,
|
|
12
|
+
dimmed when hidden. */
|
|
13
|
+
.t4-tg{display:inline-flex;align-items:center;justify-content:center;width:28px;height:26px;background:#1e293b;color:#94a3b8;border:1px solid #334155;border-radius:6px;cursor:pointer;opacity:.5}
|
|
14
|
+
.t4-tg svg{width:15px;height:15px}
|
|
15
|
+
.t4-tg.active{background:#334155;color:#fff;opacity:1}
|
|
16
|
+
.t4-dim{color:#64748b;font:11px/1 ui-monospace,monospace;margin:0 6px}
|
|
17
|
+
.t4-popout{background:#0f766e;color:#fff;border:1px solid #0f766e;border-radius:6px;padding:3px 10px;font:12px system-ui;cursor:pointer;margin-left:6px}
|
|
18
|
+
.t4-split{display:flex;height:calc(100vh - 41px);width:100vw}
|
|
19
|
+
.t4-left{flex:0 0 auto;width:58%;min-width:0;height:100%;overflow:hidden}
|
|
20
|
+
.t4-left iframe{width:100%;height:100%;border:0;display:block}
|
|
21
|
+
.t4-gutter{flex:0 0 6px;cursor:col-resize;background:#1f2937}
|
|
22
|
+
.t4-gutter:hover,.t4-gutter.drag{background:#2563eb}
|
|
23
|
+
.t4-right{flex:1 1 auto;min-width:0;height:100%;overflow:hidden;display:flex;flex-direction:column}
|
|
24
|
+
/* A LIGHT backdrop so the dark device frame stands out. */
|
|
25
|
+
.t4-stage{flex:1 1 auto;min-width:0;min-height:0;position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;background:radial-gradient(circle at 50% 32%,#f8fafc,#c3ccd9)}
|
|
26
|
+
/* The device frame - a phone/tablet bezel around the live app. overflow:hidden
|
|
27
|
+
+ border-radius CLIP the app to the rounded frame, so content is contained.
|
|
28
|
+
No width/height transition: fit() measures right after a toggle, and an
|
|
29
|
+
animated size would be read mid-transition (the "clicking does nothing" bug). */
|
|
30
|
+
.t4-device{position:relative;background:#111;box-shadow:0 16px 50px rgba(15,23,42,.45);transform-origin:center center;overflow:hidden}
|
|
31
|
+
.t4-device iframe{width:100%;height:100%;border:0;display:block;background:#fff}
|
|
32
|
+
.t4-device.mobile{border:14px solid #111;border-radius:46px}
|
|
33
|
+
.t4-device.tablet{border:16px solid #111;border-radius:26px}
|
|
34
|
+
/* Desktop fills the whole preview block - no bezel, no scaling. */
|
|
35
|
+
.t4-device.desktop{border:0;border-radius:0;box-shadow:none;width:100%;height:100%}
|
|
36
|
+
.t4-hidden{display:none !important}
|
|
37
|
+
/* While dragging the gutter, let mouse events pass over the iframes. */
|
|
38
|
+
body.t4-dragging iframe{pointer-events:none}
|
|
39
|
+
</style></head>
|
|
40
|
+
<body>
|
|
41
|
+
<div class="t4-topbar">
|
|
42
|
+
<span class="t4-brand">Tina4 Dev</span>
|
|
43
|
+
<button class="t4-tg active" id="t4-tg-files" title="Show/hide the file navigator"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4"><rect x="1.5" y="2.5" width="13" height="11" rx="1.5"/><line x1="6" y1="2.5" x2="6" y2="13.5"/></svg></button>
|
|
44
|
+
<button class="t4-tg active" id="t4-tg-editor" title="Show/hide the code editor"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="5.5,4.5 2,8 5.5,11.5"/><polyline points="10.5,4.5 14,8 10.5,11.5"/></svg></button>
|
|
45
|
+
<button class="t4-tg active" id="t4-tg-preview" title="Show/hide the preview"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"><rect x="1.5" y="2.5" width="13" height="9" rx="1.5"/><line x1="5.5" y1="14" x2="10.5" y2="14"/><line x1="8" y1="11.5" x2="8" y2="14"/></svg></button>
|
|
46
|
+
<span class="t4-spacer"></span>
|
|
47
|
+
<span class="t4-dim" id="t4-dim">1440 x 900</span>
|
|
48
|
+
<button class="t4-devbtn" data-mode="mobile">Mobile</button>
|
|
49
|
+
<button class="t4-devbtn" data-mode="tablet">Tablet</button>
|
|
50
|
+
<button class="t4-devbtn active" data-mode="desktop">Desktop</button>
|
|
51
|
+
<button class="t4-popout" id="t4-popout" title="Open this view in its own window - drag it to another screen">⇱ Pop out</button>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="t4-split" id="t4-split">
|
|
54
|
+
<div class="t4-left" id="t4-left"><iframe src="/__dev" title="Tina4 Dev Admin"></iframe></div>
|
|
55
|
+
<div class="t4-gutter" id="t4-gutter"></div>
|
|
56
|
+
<div class="t4-right" id="t4-right">
|
|
57
|
+
<div class="t4-stage" id="t4-stage">
|
|
58
|
+
<div class="t4-device desktop" id="t4-device">
|
|
59
|
+
<iframe id="t4-preview" src="/" title="Live preview"></iframe>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<script>
|
|
65
|
+
(function () {
|
|
66
|
+
var SIZES = { mobile: {w:390,h:844}, tablet: {w:820,h:1180}, desktop: {w:1440,h:900} };
|
|
67
|
+
var KEY = 'tina4_split_device';
|
|
68
|
+
var device = document.getElementById('t4-device');
|
|
69
|
+
var stage = document.getElementById('t4-stage');
|
|
70
|
+
var dim = document.getElementById('t4-dim');
|
|
71
|
+
var left = document.getElementById('t4-left');
|
|
72
|
+
var right = document.getElementById('t4-right');
|
|
73
|
+
var gutter = document.getElementById('t4-gutter');
|
|
74
|
+
var split = document.getElementById('t4-split');
|
|
75
|
+
var mode = 'desktop';
|
|
76
|
+
try { var saved = localStorage.getItem(KEY); if (saved && SIZES[saved]) mode = saved; } catch (_) {}
|
|
77
|
+
var leftWidth = '58%';
|
|
78
|
+
|
|
79
|
+
// ── device preview ──
|
|
80
|
+
function fit() {
|
|
81
|
+
if (right.classList.contains('t4-hidden')) return;
|
|
82
|
+
if (mode === 'desktop') {
|
|
83
|
+
// Desktop just fills the preview block - responsive, no frame, no scale.
|
|
84
|
+
device.style.transform = 'none';
|
|
85
|
+
device.style.width = '100%';
|
|
86
|
+
device.style.height = '100%';
|
|
87
|
+
var r0 = stage.getBoundingClientRect();
|
|
88
|
+
if (dim) dim.textContent = Math.round(r0.width) + ' x ' + Math.round(r0.height);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
var s = SIZES[mode];
|
|
92
|
+
device.style.transform = 'none';
|
|
93
|
+
device.style.width = s.w + 'px';
|
|
94
|
+
device.style.height = s.h + 'px';
|
|
95
|
+
var rect = stage.getBoundingClientRect();
|
|
96
|
+
var pad = 28;
|
|
97
|
+
var ow = device.offsetWidth, oh = device.offsetHeight;
|
|
98
|
+
var scale = Math.min((rect.width - pad) / ow, (rect.height - pad) / oh, 1);
|
|
99
|
+
if (!isFinite(scale) || scale <= 0) scale = 1;
|
|
100
|
+
device.style.transform = 'scale(' + scale + ')';
|
|
101
|
+
}
|
|
102
|
+
function setMode(m) {
|
|
103
|
+
if (!SIZES[m]) return;
|
|
104
|
+
mode = m;
|
|
105
|
+
device.className = 't4-device ' + m;
|
|
106
|
+
if (dim) dim.textContent = SIZES[m].w + ' x ' + SIZES[m].h;
|
|
107
|
+
document.querySelectorAll('.t4-devbtn').forEach(function (b) {
|
|
108
|
+
b.classList.toggle('active', b.getAttribute('data-mode') === m);
|
|
109
|
+
});
|
|
110
|
+
try { localStorage.setItem(KEY, m); } catch (_) {}
|
|
111
|
+
fit();
|
|
112
|
+
}
|
|
113
|
+
document.querySelectorAll('.t4-devbtn').forEach(function (b) {
|
|
114
|
+
b.addEventListener('click', function () { setMode(b.getAttribute('data-mode')); });
|
|
115
|
+
});
|
|
116
|
+
var pop = document.getElementById('t4-popout');
|
|
117
|
+
if (pop) pop.addEventListener('click', function () {
|
|
118
|
+
var s = SIZES[mode];
|
|
119
|
+
var w = Math.min(s.w, (screen.availWidth || 1920) - 40);
|
|
120
|
+
var h = Math.min(s.h, (screen.availHeight || 1080) - 120);
|
|
121
|
+
window.open('/', 'tina4preview_' + mode, 'width=' + w + ',height=' + h + ',menubar=no,toolbar=no,location=no');
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ── show / hide each panel (Files, Editor, Preview) ──
|
|
125
|
+
// Files (the nav) and Editor (the code area) are SEPARATE sections living
|
|
126
|
+
// inside the left /__dev SPA iframe, so hiding the editor must not take the
|
|
127
|
+
// file nav with it. They are same-origin, so we reach into the iframe and
|
|
128
|
+
// toggle its own `#editor-sidebar` (nav) and `.editor-main` (editor). The
|
|
129
|
+
// Preview is the right pane of this page.
|
|
130
|
+
var vis = { files: true, editor: true, preview: true };
|
|
131
|
+
function frameDoc() {
|
|
132
|
+
try { return document.querySelector('#t4-left iframe').contentDocument; } catch (_) { return null; }
|
|
133
|
+
}
|
|
134
|
+
function setFrameEl(sel, show) {
|
|
135
|
+
var d = frameDoc(); if (!d) return;
|
|
136
|
+
var el = d.querySelector(sel); if (el) el.style.display = show ? '' : 'none';
|
|
137
|
+
}
|
|
138
|
+
function applyPanels() {
|
|
139
|
+
setFrameEl('#editor-sidebar', vis.files);
|
|
140
|
+
setFrameEl('#editor-splitter-left', vis.files && vis.editor);
|
|
141
|
+
setFrameEl('.editor-main', vis.editor);
|
|
142
|
+
var leftEmpty = !vis.files && !vis.editor;
|
|
143
|
+
left.classList.toggle('t4-hidden', leftEmpty);
|
|
144
|
+
right.classList.toggle('t4-hidden', !vis.preview);
|
|
145
|
+
gutter.classList.toggle('t4-hidden', leftEmpty || !vis.preview);
|
|
146
|
+
if (!vis.preview) {
|
|
147
|
+
// Preview hidden: the editor/nav fills the whole width.
|
|
148
|
+
left.style.flex = '1 1 auto'; left.style.width = 'auto';
|
|
149
|
+
} else if (vis.files && !vis.editor) {
|
|
150
|
+
// Only the file nav is shown: shrink the left pane to the nav width so
|
|
151
|
+
// there is no empty gap where the editor used to be.
|
|
152
|
+
left.style.flex = '0 0 auto'; left.style.width = '250px';
|
|
153
|
+
} else {
|
|
154
|
+
left.style.flex = '0 0 auto'; left.style.width = leftWidth;
|
|
155
|
+
}
|
|
156
|
+
document.getElementById('t4-tg-files').classList.toggle('active', vis.files);
|
|
157
|
+
document.getElementById('t4-tg-editor').classList.toggle('active', vis.editor);
|
|
158
|
+
document.getElementById('t4-tg-preview').classList.toggle('active', vis.preview);
|
|
159
|
+
fit();
|
|
160
|
+
}
|
|
161
|
+
function toggle(name) {
|
|
162
|
+
// Never hide all three - at least one stays visible.
|
|
163
|
+
var n = { files: vis.files, editor: vis.editor, preview: vis.preview };
|
|
164
|
+
n[name] = !n[name];
|
|
165
|
+
if (!n.files && !n.editor && !n.preview) return;
|
|
166
|
+
vis[name] = !vis[name];
|
|
167
|
+
applyPanels();
|
|
168
|
+
}
|
|
169
|
+
document.getElementById('t4-tg-files').addEventListener('click', function () { toggle('files'); });
|
|
170
|
+
document.getElementById('t4-tg-editor').addEventListener('click', function () { toggle('editor'); });
|
|
171
|
+
document.getElementById('t4-tg-preview').addEventListener('click', function () { toggle('preview'); });
|
|
172
|
+
|
|
173
|
+
// ── drag the gutter to resize ──
|
|
174
|
+
var dragging = false;
|
|
175
|
+
gutter.addEventListener('mousedown', function (e) {
|
|
176
|
+
dragging = true;
|
|
177
|
+
document.body.classList.add('t4-dragging');
|
|
178
|
+
gutter.classList.add('drag');
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
});
|
|
181
|
+
window.addEventListener('mousemove', function (e) {
|
|
182
|
+
if (!dragging) return;
|
|
183
|
+
var r = split.getBoundingClientRect();
|
|
184
|
+
var w = e.clientX - r.left;
|
|
185
|
+
w = Math.max(220, Math.min(w, r.width - 260));
|
|
186
|
+
leftWidth = w + 'px';
|
|
187
|
+
left.style.width = leftWidth;
|
|
188
|
+
fit();
|
|
189
|
+
});
|
|
190
|
+
window.addEventListener('mouseup', function () {
|
|
191
|
+
if (!dragging) return;
|
|
192
|
+
dragging = false;
|
|
193
|
+
document.body.classList.remove('t4-dragging');
|
|
194
|
+
gutter.classList.remove('drag');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
window.addEventListener('resize', fit);
|
|
198
|
+
setMode(mode);
|
|
199
|
+
applyPanels();
|
|
200
|
+
})();
|
|
201
|
+
</script>
|
|
202
|
+
</body></html>
|