Skip to content
Draft
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ The preset does not fetch image URLs, inspect a store, validate dynamic Markets/
cover every column dependency, or guarantee that Shopify accepts a file. Shopify supports older
headers and dynamic columns, so the tool avoids pretending it can reject every unknown header.

### Exact-error triage

`shopify-csv-error-triage.html` accepts only one Shopify import error message, matches it against
the current documented error families, and explains whether the next check belongs to the local
file, store state, or an external image/taxonomy resource. The classifier is deterministic and
local-only: it has no upload, store login, fetch, storage, analytics, or unknown-error guess. It is
an entry point into the existing free checker, not a claim that every Shopify error is detectable.

## Fixed-scope cleanup pilot

The completed **CSV Preflight Pro offline batch edition** is offered as a 15,000 sat Personal
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>CSV Preflight — local CSV and Shopify product import checker</title><meta name="description" content="Check CSV structure locally, with an optional Shopify product CSV preset for documented headers and values. No upload or signup."><link rel="canonical" href="https://softpeanut.github.io/csv-preflight/"><meta property="og:title" content="CSV Preflight — local CSV and Shopify product import checker"><meta property="og:description" content="Check generic CSV structure or a bounded set of documented Shopify product CSV rules without uploading your data."><meta property="og:url" content="https://softpeanut.github.io/csv-preflight/"><meta property="og:type" content="website"><meta name="twitter:card" content="summary"><link rel="icon" href="logo.svg" type="image/svg+xml"><link rel="stylesheet" href="styles.css"><script type="application/ld+json">{"@context":"https://schema.org","@type":"SoftwareApplication","name":"CSV Preflight","url":"https://softpeanut.github.io/csv-preflight/","applicationCategory":"DeveloperApplication","operatingSystem":"Any","description":"Check generic CSV structure or a bounded set of documented Shopify product CSV rules locally without uploading files.","isAccessibleForFree":true,"offers":{"@type":"Offer","price":"0","priceCurrency":"USD"}}</script></head>
<body><main><p><a href="article.html">Read: CSV Is a State Machine</a> · <a href="case-study.html">Checkable example</a> · <a href="csv-ci-case-study.html">Public CI case study</a> · <a href="ko.html" lang="ko">한국어 서비스</a></p><header><span class="eyebrow">LOCAL-ONLY CSV CHECK</span><h1>Catch import failures<br>before your importer does.</h1><p>Drop or paste CSV. Nothing leaves your browser.</p></header>
<body><main><p><a href="article.html">Read: CSV Is a State Machine</a> · <a href="case-study.html">Checkable example</a> · <a href="csv-ci-case-study.html">Public CI case study</a> · <a href="shopify-csv-error-triage.html">Shopify error triage</a> · <a href="ko.html" lang="ko">한국어 서비스</a></p><header><span class="eyebrow">LOCAL-ONLY CSV CHECK</span><h1>Catch import failures<br>before your importer does.</h1><p>Drop or paste CSV. Nothing leaves your browser.</p></header>
<aside class="tip"><div><strong>Did the free checker save you time?</strong><span>An optional Lightning tip buys no cleanup, support, feature, or import guarantee. It is separate from the fixed-scope service.</span></div><a href="lightning:softpeanut@stacker.news">Tip 2+ sats · softpeanut@stacker.news</a></aside>
<section id="drop" class="card drop"><input id="file" type="file" accept=".csv,text/csv"><label for="file"><strong>Drop a CSV here</strong><span>or choose a file</span></label><div class="or">OR PASTE</div><textarea id="input" placeholder="email,name&#10;ada@example.com,Ada"></textarea><div class="profile"><label for="profile">Validation profile</label><select id="profile"><option value="generic">Generic CSV structure</option><option value="shopify:create">Shopify — new products</option><option value="shopify:update">Shopify — update products</option></select><small>Shopify preset reviewed against the <a href="https://help.shopify.com/en/manual/products/import-export/using-csv" target="_blank" rel="noopener">official product CSV guide</a> on 2026-08-27. It checks a bounded set of documented rules and does not guarantee import success.</small></div><button id="check">Run preflight</button></section>
<section id="results" class="card" hidden><div class="summary"><div><small>ENCODING</small><strong id="encoding">Pasted text</strong></div><div><small>DELIMITER</small><strong id="delimiter">Comma</strong></div><div><small>ROWS</small><strong id="rows">0</strong></div><div><small>ISSUES</small><strong id="count">0</strong></div></div><h2>Preflight report</h2><ul id="issues"></ul><div class="actions"><button id="csvDownload">Download cleaned CSV</button><button id="reportDownload" class="secondary">Download error report</button></div></section>
Expand Down
11 changes: 11 additions & 0 deletions offer.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const batchTemplate = readFileSync(new URL("./.github/ISSUE_TEMPLATE/batch-licen
const batchTemplateKo = readFileSync(new URL("./.github/ISSUE_TEMPLATE/batch-license-ko.yml", import.meta.url), "utf8");
const action = readFileSync(new URL("./action.yml", import.meta.url), "utf8");
const shopifyGuideKo = readFileSync(new URL("./shopify-csv-guide-ko.html", import.meta.url), "utf8");
const shopifyErrorTriage = readFileSync(new URL("./shopify-csv-error-triage.html", import.meta.url), "utf8");
const githubActionGuide = readFileSync(new URL("./validate-csv-github-actions.html", import.meta.url), "utf8");
const ciSetupTerms = readFileSync(new URL("./ci-setup-terms.html", import.meta.url), "utf8");
const ciSetupTermsZhCn = readFileSync(new URL("./ci-setup-terms-zh-CN.html", import.meta.url), "utf8");
Expand Down Expand Up @@ -189,9 +190,18 @@ test("publishes a bounded Korean Shopify CSV troubleshooting guide", () => {
assert.match(shopifyGuideKo, /닫히지 않은 따옴표/);
assert.match(shopifyGuideKo, /가져오기 성공 보장이 아니라/);
assert.match(shopifyGuideKo, /help\.shopify\.com\/en\/manual\/products\/import-export\/common-import-issues/);
assert.match(shopifyGuideKo, /shopify-csv-error-triage\.html/);
assert.doesNotMatch(shopifyGuideKo, /공식 도구|Shopify 인증|모든 오류/);
});

test("links the exact-error triage entry point without overstating its scope", () => {
assert.match(page, /shopify-csv-error-triage\.html/);
assert.match(shopifyErrorTriage, /Nothing is uploaded, stored, or sent/);
assert.match(shopifyErrorTriage, /Unknown messages stay unknown/);
assert.match(shopifyErrorTriage, /not affiliated with Shopify/);
assert.doesNotMatch(shopifyErrorTriage, /Shopify approved|official checker|guaranteed fix/i);
});

test("publishes truthful search metadata for every public page", () => {
const structuredDataMatch = page.match(
/<script type="application\/ld\+json">([^<]+)<\/script>/,
Expand All @@ -211,6 +221,7 @@ test("publishes truthful search metadata for every public page", () => {
"https://softpeanut.github.io/csv-preflight/pro-terms.html",
"https://softpeanut.github.io/csv-preflight/pro-terms-ko.html",
"https://softpeanut.github.io/csv-preflight/shopify-csv-guide-ko.html",
"https://softpeanut.github.io/csv-preflight/shopify-csv-error-triage.html",
"https://softpeanut.github.io/csv-preflight/validate-csv-github-actions.html",
"https://softpeanut.github.io/csv-preflight/ci-setup-terms.html",
"https://softpeanut.github.io/csv-preflight/ci-setup-terms-zh-CN.html",
Expand Down
62 changes: 62 additions & 0 deletions shopify-csv-error-triage.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Shopify CSV Error Triage — explain an import error locally</title>
<meta name="description" content="Paste one Shopify product CSV import error and get a bounded explanation and next step. Nothing is uploaded or stored.">
<link rel="canonical" href="https://softpeanut.github.io/csv-preflight/shopify-csv-error-triage.html">
<meta property="og:type" content="website">
<meta property="og:title" content="Shopify CSV Error Triage">
<meta property="og:description" content="Classify one Shopify product-import error locally without uploading a CSV.">
<meta property="og:url" content="https://softpeanut.github.io/csv-preflight/shopify-csv-error-triage.html">
<link rel="icon" href="logo.svg" type="image/svg+xml">
<link rel="stylesheet" href="styles.css">
<style>
.triage{max-width:800px}.triage h1{font-size:clamp(42px,8vw,72px)}
.triage textarea{min-height:110px}.triage .privacy{color:#536159}
.triage-result{margin-top:24px}.triage-result h2{margin:.35rem 0 1rem}
.triage-result p{font-size:16px;line-height:1.65}.triage-result .scope{padding:12px 14px;background:#edf1e9;border-radius:8px}
.next-actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:28px}.next-actions a{border-radius:8px;background:#193d2d;color:#fff;padding:12px 18px;font-weight:750;text-decoration:none}
.next-actions a.secondary{background:#e3e9e4;color:#193d2d}
</style>
</head>
<body>
<main class="triage">
<p><a href="./">← CSV Preflight</a> · <a href="shopify-csv-guide-ko.html" lang="ko">한국어 가져오기 점검표</a></p>
<header>
<span class="eyebrow">SHOPIFY PRODUCT CSV · LOCAL ERROR TRIAGE</span>
<h1>Start with the error,<br>not the catalog.</h1>
<p>Paste one exact Shopify product-import error. The match and explanation happen in this browser tab.</p>
</header>

<section class="card">
<form id="triage-form">
<label for="error-text"><strong>Exact error message</strong></label>
<textarea id="error-text" maxlength="500" placeholder="Line 2: Validation failed: options are not unique"></textarea>
<p class="privacy">Nothing is uploaded, stored, or sent. Do not paste CSV rows, product or customer data, credentials, store URLs, emails, or private links.</p>
<button type="submit">Explain this error</button>
</form>
</section>

<section id="triage-result" class="card triage-result" hidden aria-live="polite">
<span id="result-status" class="eyebrow"></span>
<h2 id="result-title"></h2>
<p id="result-cause"></p>
<p id="result-next"></p>
<p id="result-scope" class="scope"></p>
<p><a id="result-source" target="_blank" rel="noopener"></a></p>
</section>

<div class="next-actions">
<a href="./">Run the full local CSV preflight</a>
<a class="secondary" href="https://github.com/softpeanut/csv-preflight/issues/new?template=csv-cleanup.yml">Check $99 rescue scope</a>
</div>
<p class="privacy">This independent tool is not affiliated with Shopify. It does not access a store, fetch images, validate every business rule, or guarantee an import. A public GitHub issue is only a fit check—never attach data or private links.</p>

<h2>Sources and limits</h2>
<p>Error families and next steps were reviewed against Shopify's <a href="https://help.shopify.com/en/manual/products/import-export/common-import-issues">common product CSV problems</a> and <a href="https://help.shopify.com/en/manual/products/import-export/import-products">product import guide</a> on 2026-08-28. The 10,000-reference message comes from a <a href="https://community.shopify.com/t/csv-import-issue/645056">current Shopify Community report</a> and is labeled community-observed rather than official documentation. Unknown messages stay unknown instead of being forced into a diagnosis.</p>
</main>
<script type="module" src="shopify-error-triage-app.mjs"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion shopify-csv-guide-ko.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</head>
<body>
<main class="guide">
<p><a href="ko.html">← CSV Preflight 한국어 서비스</a> · <a href="index.html">무료 검사기</a></p>
<p><a href="ko.html">← CSV Preflight 한국어 서비스</a> · <a href="index.html">무료 검사기</a> · <a href="shopify-csv-error-triage.html" lang="en">오류 메시지 진단</a></p>
<header>
<span class="eyebrow">SHOPIFY PRODUCT CSV PREFLIGHT</span>
<h1>Shopify 상품 CSV,<br>가져오기 전에 볼 5가지</h1>
Expand Down
75 changes: 75 additions & 0 deletions shopify-error-triage-app.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { classifyShopifyError } from "./shopify-error-triage.mjs";

const required = [
"triage-form",
"error-text",
"triage-result",
"result-status",
"result-title",
"result-cause",
"result-next",
"result-scope",
"result-source",
];

const nodes = Object.fromEntries(required.map((id) => [id, document.getElementById(id)]));
const missing = required.filter((id) => !nodes[id]);
if (missing.length) throw new Error(`Shopify error triage is missing required nodes: ${missing.join(", ")}`);

function setText(node, value) {
node.textContent = value ?? "";
}

function render(result) {
nodes["triage-result"].hidden = false;
nodes["result-cause"].hidden = result.status !== "matched";
nodes["result-scope"].hidden = result.status !== "matched";

if (result.status === "empty") {
setText(nodes["result-status"], "INPUT NEEDED");
setText(nodes["result-title"], "Paste only Shopify's exact error message.");
setText(nodes["result-next"], "Do not paste CSV rows, product data, customer data, credentials, or private links.");
nodes["result-source"].removeAttribute("href");
setText(nodes["result-source"], "No text was analyzed");
return;
}

if (result.status === "too_long") {
setText(nodes["result-status"], "INPUT TOO LONG");
setText(nodes["result-title"], `Use only the exact error message, up to ${result.maxLength} characters.`);
setText(nodes["result-next"], "Remove surrounding CSV content, email text, store details, and other context before trying again.");
nodes["result-source"].removeAttribute("href");
setText(nodes["result-source"], "No text was analyzed");
return;
}

if (result.status === "unknown") {
setText(nodes["result-status"], "NO DOCUMENTED MATCH");
setText(nodes["result-title"], result.title);
setText(nodes["result-next"], result.nextStep);
} else {
setText(nodes["result-status"], result.confidence === "official-documented" ? "DOCUMENTED MATCH" : "COMMUNITY-OBSERVED MATCH");
setText(nodes["result-title"], result.title);
setText(nodes["result-cause"], `Likely cause: ${result.cause}`);
setText(nodes["result-next"], `Next safe step: ${result.nextStep}`);
setText(nodes["result-scope"], `${result.scopeLabel}: ${result.checkerFit}`);
}

nodes["result-source"].href = result.sourceUrl;
setText(nodes["result-source"], `Source: ${result.sourceLabel}`);
}

nodes["triage-form"].addEventListener("submit", (event) => {
event.preventDefault();
try {
render(classifyShopifyError(nodes["error-text"].value));
} catch {
render({
status: "unknown",
title: "This message could not be classified locally",
nextStep: "Use Shopify's current troubleshooting page. No input was stored or sent.",
sourceUrl: "https://help.shopify.com/en/manual/products/import-export/common-import-issues",
sourceLabel: "Shopify Help Center",
});
}
});
Loading
Loading