Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
node_modules
.lighthouseci
.lhtmp
20 changes: 10 additions & 10 deletions legacy-script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3294,8 +3294,8 @@ function renderProjectDetail(language = currentSiteLanguage || "en") {
if (!root) return;

const params = new URLSearchParams(window.location.search);
const slug = params.get("project") || "drivenfinity";
const project = projectDetailData[slug];
const slug = params.get("project");
const project = slug ? projectDetailData[slug] : null;

if (!project) {
root.innerHTML = `
Expand Down Expand Up @@ -4868,8 +4868,8 @@ const ultimateContent = {
label: "Open 3D Math Lab",
hint: "Interactive canvas model",
keywords: "3d math canvas model rotate",
type: "scroll",
value: "algorithmic-3d-lab",
type: "nav",
value: "labs.html#algorithmic-3d-lab",
},
{
id: "easter",
Expand Down Expand Up @@ -5065,8 +5065,8 @@ const ultimateContent = {
label: "3D Matematik Labını Aç",
hint: "İnteraktif canvas modeli",
keywords: "3d matematik canvas model döndür",
type: "scroll",
value: "algorithmic-3d-lab",
type: "nav",
value: "labs.html#algorithmic-3d-lab",
},
{
id: "easter",
Expand Down Expand Up @@ -6171,8 +6171,8 @@ function extendCreativeCommands() {
label: "Open 3D Math Lab",
hint: "Interactive canvas model",
keywords: "3d math canvas model rotate",
type: "scroll",
value: "algorithmic-3d-lab",
type: "nav",
value: "labs.html#algorithmic-3d-lab",
});
if (!hasCommand("en", "easter"))
ultimateContent.en.commands.push({
Expand All @@ -6188,8 +6188,8 @@ function extendCreativeCommands() {
label: "3D Matematik Labını Aç",
hint: "İnteraktif canvas modeli",
keywords: "3d matematik canvas model döndür",
type: "scroll",
value: "algorithmic-3d-lab",
type: "nav",
value: "labs.html#algorithmic-3d-lab",
});
if (!hasCommand("tr", "easter"))
ultimateContent.tr.commands.push({
Expand Down
10 changes: 5 additions & 5 deletions portfolio-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,9 +237,9 @@
status: "READY",
summary: t("Required evidence is collected before the side effect is allowed.", "Side effect'e izin verilmeden önce gerekli kanıtlar toplanıyor."),
conversation: [
{ speaker: "Customer", text: t("My car was damaged. Can you open a claim?", "Aracım hasar gördü. Hasar kaydı açabilir misiniz?") },
{ speaker: "Agent", text: t("I can help. First I need to verify your contact details and collect the required damage documents.", "Yardımcı olabilirim. Önce iletişim bilgilerinizi doğrulayıp gerekli hasar belgelerini toplamam gerekiyor.") },
{ speaker: "Customer", text: t("Okay, I can send the photos now.", "Tamam, fotoğrafları şimdi gönderebilirim.") }
{ speaker: t("Customer", "Müşteri"), text: t("My car was damaged. Can you open a claim?", "Aracım hasar gördü. Hasar kaydı açabilir misiniz?") },
{ speaker: t("Agent", "Agent"), text: t("I can help. First I need to verify your contact details and collect the required damage documents.", "Yardımcı olabilirim. Önce iletişim bilgilerinizi doğrulayıp gerekli hasar belgelerini toplamam gerekiyor.") },
{ speaker: t("Customer", "Müşteri"), text: t("Okay, I can send the photos now.", "Tamam, fotoğrafları şimdi gönderebilirim.") }
],
toolTrace: ["verify_contact()", "collect_required_documents()", "submit_claim()"],
findings: [
Expand All @@ -253,8 +253,8 @@
status: "BLOCKED",
summary: t("The agent performs a claim-submission side effect before collecting prerequisites.", "Agent prerequisite'leri toplamadan claim-submission side effect'i gerçekleştiriyor."),
conversation: [
{ speaker: "Customer", text: t("My car was damaged. Can you open a claim?", "Aracım hasar gördü. Hasar kaydı açabilir misiniz?") },
{ speaker: "Agent", text: t("Sure. I am submitting the claim immediately.", "Tabii. Hasar kaydını hemen oluşturuyorum.") }
{ speaker: t("Customer", "Müşteri"), text: t("My car was damaged. Can you open a claim?", "Aracım hasar gördü. Hasar kaydı açabilir misiniz?") },
{ speaker: t("Agent", "Agent"), text: t("Sure. I am submitting the claim immediately.", "Tabii. Hasar kaydını hemen oluşturuyorum.") }
],
toolTrace: ["submit_claim()"],
findings: [
Expand Down
27 changes: 16 additions & 11 deletions portfolio-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,11 +266,8 @@
}

function buildLogMarkup(entry, language) {
const statusLabel = {
shipped: language === "tr" ? "Shipped" : "Shipped",
building: language === "tr" ? "Building" : "Building",
integration: language === "tr" ? "Integration" : "Integration"
}[entry.status] || entry.status;
// Release-state names are product terms and stay identical in both languages.
const statusLabel = { shipped: "Shipped", building: "Building", integration: "Integration" }[entry.status] || entry.status;
return `<article class="build-log-item"><time datetime="${esc(entry.date)}">${esc(entry.date)}</time><div><div class="build-log-meta"><span>${esc(entry.area)}</span><span class="build-log-status is-${esc(entry.status)}">${esc(statusLabel)}</span></div><h3>${esc(pick(entry.title, language))}</h3><p>${esc(pick(entry.detail, language))}</p></div></article>`;
}

Expand All @@ -292,17 +289,24 @@
if (!root) return;
const language = lang();
const scenario = registry.sinamaEvidence[state.evidenceScenario];
// Keep focus inside the explorer when a scenario switch re-renders the markup.
const hadFocus = root.contains(document.activeElement);
const labels = language === "tr"
? { conversation: "Conversation", tools: "Tool Trace", evaluation: "Evaluation", healthy: "Healthy Run", broken: "Broken Run", verdict: "Release verdict" }
: { conversation: "Conversation", tools: "Tool Trace", evaluation: "Evaluation", healthy: "Healthy Run", broken: "Broken Run", verdict: "Release verdict" };
? { conversation: "Konuşma", tools: "Tool Trace", evaluation: "Değerlendirme", verdict: "Release kararı", group: "SINAMA örnek çalıştırması" }
: { conversation: "Conversation", tools: "Tool Trace", evaluation: "Evaluation", verdict: "Release verdict", group: "SINAMA example run" };
// Scenario button labels stay registry-owned so the explorer cannot drift from portfolio-data.js.
const scenarioButton = (id) => {
const isActive = state.evidenceScenario === id;
return `<button type="button" data-evidence-scenario="${esc(id)}" class="${isActive ? "active" : ""}" aria-pressed="${isActive}">${esc(pick(registry.sinamaEvidence[id].label, language))}</button>`;
};
root.innerHTML = `
<div class="evidence-toolbar" role="group" aria-label="SINAMA example run">
<button type="button" data-evidence-scenario="healthy" class="${state.evidenceScenario === "healthy" ? "active" : ""}" aria-pressed="${state.evidenceScenario === "healthy"}">${esc(labels.healthy)}</button>
<button type="button" data-evidence-scenario="broken" class="${state.evidenceScenario === "broken" ? "active" : ""}" aria-pressed="${state.evidenceScenario === "broken"}">${esc(labels.broken)}</button>
<div class="evidence-toolbar" role="group" aria-label="${esc(labels.group)}">
${scenarioButton("healthy")}
${scenarioButton("broken")}
</div>
<div class="evidence-verdict is-${scenario.status.toLowerCase()}"><span>${esc(labels.verdict)}</span><strong>${esc(scenario.status)}</strong><p>${esc(pick(scenario.summary, language))}</p></div>
<div class="evidence-grid">
<article><p class="eyebrow">${esc(labels.conversation)}</p><div class="evidence-conversation">${scenario.conversation.map((turn) => `<div><strong>${esc(turn.speaker)}</strong><p>${esc(pick(turn.text, language))}</p></div>`).join("")}</div></article>
<article><p class="eyebrow">${esc(labels.conversation)}</p><div class="evidence-conversation">${scenario.conversation.map((turn) => `<div><strong>${esc(pick(turn.speaker, language))}</strong><p>${esc(pick(turn.text, language))}</p></div>`).join("")}</div></article>
<article><p class="eyebrow">${esc(labels.tools)}</p><ol class="evidence-tools">${scenario.toolTrace.map((tool) => `<li><code>${esc(tool)}</code></li>`).join("")}</ol></article>
<article><p class="eyebrow">${esc(labels.evaluation)}</p><ul class="evidence-findings">${scenario.findings.map((finding) => `<li class="is-${finding.level.toLowerCase()}"><strong>${esc(finding.level)}</strong><span>${esc(pick(finding.text, language))}</span></li>`).join("")}</ul></article>
</div>`;
Expand All @@ -312,6 +316,7 @@
renderEvidenceExplorer();
});
});
if (hadFocus) root.querySelector(`[data-evidence-scenario="${state.evidenceScenario}"]`)?.focus();
}

function installRequestReceipt() {
Expand Down
77 changes: 77 additions & 0 deletions qa-portfolio-consistency.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,83 @@ const normalizedArchitecture = architecture.toLowerCase();
check(normalizedArchitecture.includes("source-of-truth"), "architecture document must keep the source-of-truth rule");
check(normalizedArchitecture.includes("does not claim a live llm"), "architecture document must keep Ajoop scope explicit");

// --- Runtime boot integrity -------------------------------------------------
// The bootloader is the highest-risk part of V2: a page either declares the
// registry + V2 runtime itself, or relies on script.js to inject them. Both
// paths must stay unambiguous and free of duplicate runtime loads.
const runtimeFiles = ["portfolio-data.js", "script.js", "legacy-script.js", "portfolio-v2.js"];
const htmlFiles = fs.readdirSync(".").filter((file) => file.endsWith(".html"));

htmlFiles.forEach((file) => {
const source = read(file);
const scriptSrcs = [...source.matchAll(/<script[^>]+src="([^"]+)"/g)].map((match) => match[1]);

runtimeFiles.forEach((runtime) => {
const occurrences = scriptSrcs.filter((src) => src === runtime).length;
check(occurrences <= 1, `${file} declares ${runtime} ${occurrences} times (duplicate runtime load)`);
});

check(
!scriptSrcs.includes("legacy-script.js"),
`${file} must not load legacy-script.js directly; the bootloader owns that execution order`,
);

const dataIndex = scriptSrcs.indexOf("portfolio-data.js");
const bootIndex = scriptSrcs.indexOf("script.js");
const v2Index = scriptSrcs.indexOf("portfolio-v2.js");

if (dataIndex !== -1 || v2Index !== -1) {
// Migrated page: registry before the bootloader, V2 runtime after it.
check(dataIndex !== -1 && v2Index !== -1, `${file} must declare both portfolio-data.js and portfolio-v2.js, or neither`);
check(bootIndex !== -1, `${file} must still load script.js between the registry and the V2 runtime`);
check(dataIndex < bootIndex, `${file} must declare portfolio-data.js before script.js`);
check(bootIndex < v2Index, `${file} must declare portfolio-v2.js after script.js`);
check(source.includes("portfolio-v2.css"), `${file} declares the V2 runtime but never links portfolio-v2.css`);
}

check((source.match(/portfolio-v2\.css/g) || []).length <= 1, `${file} links portfolio-v2.css more than once`);

// Unified EN/TR copy must stay paired, otherwise one language silently wins.
const enCopy = (source.match(/data-pv2-en=/g) || []).length;
const trCopy = (source.match(/data-pv2-tr=/g) || []).length;
check(enCopy === trCopy, `${file} has ${enCopy} data-pv2-en vs ${trCopy} data-pv2-tr attributes; unified copy must stay paired`);
});

// --- Dynamic archive route --------------------------------------------------
const legacyRuntime = read("legacy-script.js");
check(
!/params\.get\("project"\)\s*\|\|/.test(legacyRuntime),
"project-detail must not fall back to a hardcoded slug; a missing or unknown project belongs in the not-found state",
);

// --- Command palette targets ------------------------------------------------
// Experiments moved to labs.html in V2, so same-page scroll commands would
// silently no-op on every other page.
check(
!legacyRuntime.includes('value: "algorithmic-3d-lab"'),
"the 3D lab command must navigate to labs.html#algorithmic-3d-lab instead of scrolling to a section that no longer exists on most pages",
);
check(read("labs.html").includes('id="algorithmic-3d-lab"'), "labs.html must keep the algorithmic-3d-lab anchor target");
check(
(registry?.labs || []).some((item) => (item.url || "").includes("algorithmic-3d-lab")),
"registry Labs must keep the Algorithmic 3D Lab entry that the command palette points at",
);

// --- SINAMA Evidence Explorer -----------------------------------------------
const v2Runtime = read("portfolio-v2.js");
check(
/sinamaEvidence\[[^\]]+\]\.label/.test(v2Runtime),
"the Evidence Explorer must read scenario labels from the registry instead of duplicating product truth",
);
["healthy", "broken"].forEach((id) => {
const conversation = registry?.sinamaEvidence?.[id]?.conversation || [];
check(conversation.length > 0, `sinamaEvidence.${id} needs conversation turns`);
check(
conversation.every((turn) => turn.speaker && typeof turn.speaker === "object" && turn.speaker.en && turn.speaker.tr),
`sinamaEvidence.${id} speaker names must be bilingual so the explorer is not half-English in TR`,
);
});

if (failures.length) {
console.error(`Portfolio consistency guard failed with ${failures.length} issue(s):`);
failures.forEach((failure) => console.error(`- ${failure}`));
Expand Down
Loading