{
  "sha": "962442d141a19498678a7ff152a3b1ebd2ea3e4e",
  "message": "configure SKYTICAL custom domain (#102)\n\nPublish SKYTICAL from https://skytical.tech at the custom-domain root and update SEO URLs, public paths, provider attribution, documentation, and regression coverage.",
  "author": {
    "login": "nnneeeooo-nnneeeooo",
    "name": "",
    "email": ""
  },
  "committer": {
    "login": "web-flow",
    "name": "",
    "email": ""
  },
  "created_at": "2026-08-13T16:32:04Z",
  "files": [
    {
      "filename": "README.md",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 1,
      "changes": 3
    },
    {
      "filename": "pipeline/CONTRACTS.md",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 1,
      "changes": 3
    },
    {
      "filename": "pipeline/common.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 4,
      "deletions": 4,
      "changes": 8
    },
    {
      "filename": "pipeline/providers.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "tests/test_changelog.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 2,
      "deletions": 2,
      "changes": 4
    },
    {
      "filename": "tests/test_providers_payloads.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "tests/test_radar.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "tests/test_search.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 10,
      "deletions": 10,
      "changes": 20
    },
    {
      "filename": "tests/test_seo.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 5,
      "deletions": 3,
      "changes": 8
    }
  ],
  "diff": "@@ -107,7 +107,8 @@ python -m http.server -d site 8000\\n | `AVWIRE_NVIDIA_MODEL` | `z-ai/glm-5.2` | NVIDIA NIM 模型 |\\n | `NEWS_MAX_AGE_HOURS` | `120` | 新聞新鮮度窗口（小時），驗證範圍 24–336，非法值回退預設並警告 |\\n | `AEROAPI_KEY` | — | FlightAware AeroAPI（選用統計） |\\n-| `AVWIRE_BASE_PATH` | `/avwire` | 站台子路徑（Pages 專案站台） |\\n+| `AVWIRE_BASE_PATH` | 空字串 | 站台子路徑；正式自訂網域使用根路徑 |\\n+| `AVWIRE_SITE_ORIGIN` | `https://skytical.tech` | canonical、hreflang、sitemap 與 JSON-LD 的公開站台來源 |\\n \\n ## 稀有民航機偵測（預設關閉）\\n \\n@@ -284,7 +284,8 @@ site/\\n └─ .nojekyll\\n ```\\n \\n-All internal links are prefixed with `common.BASE_PATH` (default `/avwire`).\\n+All internal links are prefixed with `common.BASE_PATH` (empty by default for\\n+the `https://skytical.tech` custom-domain root).\\n Every page rendered through `base.html` includes a language-correct footer\\n button linking to its changelog page.\\n \\n@@ -22,14 +22,14 @@\\n TEMPLATES_DIR = ROOT / \"templates\"\\n STATIC_DIR = ROOT / \"static\"\\n \\n-# GitHub Pages project sites live under /<repo>/ — override with env for\\n-# user/organization pages or local preview (set AVWIRE_BASE_PATH=\"\").\\n-BASE_PATH = os.environ.get(\"AVWIRE_BASE_PATH\", \"/avwire\").rstrip(\"/\")\\n+# The production site is served from the root of the SKYTICAL custom domain.\\n+# Override this for a project-site preview that needs a repository subpath.\\n+BASE_PATH = os.environ.get(\"AVWIRE_BASE_PATH\", \"\").rstrip(\"/\")\\n \\n # Absolute origin for hreflang/canonical links (search engines require\\n # fully-qualified URLs there).\\n SITE_ORIGIN = (os.environ.get(\"AVWIRE_SITE_ORIGIN\")\\n-               or \"https://nnneeeooo-nnneeeooo.github.io\").rstrip(\"/\")\\n+               or \"https://skytical.tech\").rstrip(\"/\")\\n \\n USER_AGENT = (\\n     \"AVWIREBot/1.0 (automated aviation news aggregator; \"\\n@@ -925,7 +925,7 @@ def _post(self, messages: list, repair: bool):\\n                     \"Accept\": \"application/json\",\\n                     \"Content-Type\": \"application/json\",\\n                     \"HTTP-Referer\":\\n-                        \"https://nnneeeooo-nnneeeooo.github.io/avwire/\",\\n+                        \"https://skytical.tech/\",\\n                     \"X-Title\": \"AVWIRE\",\\n                 },\\n             )\\n@@ -107,8 +107,8 @@ def main() -> None:\\n             description=t[\"changeSub\"], ticker=[], build=fake_build)\\n         ctx[\"changelog\"] = view\\n         html = env.get_template(\"changelog.html\").render(**ctx)\\n-        expected_path = (\"/avwire/changelog/\" if lang == \"zh\"\\n-                         else \"/avwire/en/changelog/\")\\n+        expected_path = (\"/changelog/\" if lang == \"zh\"\\n+                         else \"/en/changelog/\")\\n         assert f'href=\"{expected_path}\"' in html\\n         assert t[\"footerChangelog\"] in html\\n         assert t[\"changeNotice\"] in html\\n@@ -224,7 +224,7 @@ def fake_post(url, json=None, timeout=None, headers=None):\\n         check((payload.get(\"reasoning\") or {}).get(\"exclude\") is True,\\n               f\"{model}: reasoning trace excluded\")\\n         check(headers.get(\"X-Title\") == \"AVWIRE\"\\n-              and headers.get(\"HTTP-Referer\", \"\").endswith(\"/avwire/\"),\\n+              and headers.get(\"HTTP-Referer\") == \"https://skytical.tech/\",\\n               f\"{model}: optional attribution headers\")\\n         check(provider.usage[\"inputTokens\"] == 11\\n               and provider.usage[\"outputTokens\"] == 7\\n@@ -153,7 +153,7 @@ def main() -> None:\\n     assert \"Callsign format\" not in en\\n     assert \"台灣民航雷達\" in zh\\n     assert \"Taiwan civil flight radar\" in en\\n-    assert 'href=\"/avwire/radar/\"' in home\\n+    assert 'href=\"/radar/\"' in home\\n \\n     assert \"flags & (1 | 4 | 8)\" in js\\n     assert '[\"7500\", \"7600\", \"7700\"]' in js\\n@@ -96,21 +96,21 @@ def main() -> None:\\n     assert 'class=\"header-search-form\"' in home\\n     assert 'class=\"header-search-form\"' in article\\n     assert 'data-article-body' in article\\n-    assert 'data-search-aliases-url=\"/avwire/search-aliases.json\"' in article\\n-    assert re.search(r'/avwire/assets/highlight\\.js\\?v=[0-9a-f]{10}', article)\\n-    assert 'action=\"/avwire/search/\"' in home\\n-    assert 'action=\"/avwire/en/search/\"' in en\\n-    assert re.search(r'/avwire/assets/site\\.css\\?v=[0-9a-f]{10}', zh)\\n-    assert re.search(r'/avwire/assets/app\\.js\\?v=[0-9a-f]{10}', zh)\\n-    assert re.search(r'/avwire/assets/search\\.js\\?v=[0-9a-f]{10}', zh)\\n+    assert 'data-search-aliases-url=\"/search-aliases.json\"' in article\\n+    assert re.search(r'/assets/highlight\\.js\\?v=[0-9a-f]{10}', article)\\n+    assert 'action=\"/search/\"' in home\\n+    assert 'action=\"/en/search/\"' in en\\n+    assert re.search(r'/assets/site\\.css\\?v=[0-9a-f]{10}', zh)\\n+    assert re.search(r'/assets/app\\.js\\?v=[0-9a-f]{10}', zh)\\n+    assert re.search(r'/assets/search\\.js\\?v=[0-9a-f]{10}', zh)\\n     assert home.index('class=\"header-search-row\"') < home.index('id=\"clock-utc\"')\\n     assert home.index('class=\"header-search-row\"') < home.index('class=\"brand-row\"')\\n     main_markup = re.search(r'<main class=\"search-page\">(.*?)</main>', zh,\\n                             re.DOTALL).group(1)\\n     assert \"<form\" not in main_markup\\n-    assert 'data-index-url=\"/avwire/search-index.json\"' in zh\\n-    assert 'data-index-url=\"/avwire/search-index.json\"' in en\\n-    assert 'href=\"/avwire/en/search/\"' in en\\n+    assert 'data-index-url=\"/search-index.json\"' in zh\\n+    assert 'data-index-url=\"/search-index.json\"' in en\\n+    assert 'href=\"/en/search/\"' in en\\n     assert \"border-radius: 999px\" in css\\n     assert \".header-search-form:focus-within\" in css\\n     assert \".header-search-input:not(:placeholder-shown) + .header-search-clear\" in css\\n@@ -21,6 +21,8 @@ def _json_ld(html: str) -> list[dict]:\\n \\n \\n def main() -> None:\\n+    assert build.SITE_ORIGIN == \"https://skytical.tech\"\\n+    assert build.BASE_PATH == \"\"\\n     assert build.main() == 0\\n \\n     verification = ROOT / \"site\" / \"google83f7fe8f9aeba973.html\"\\n@@ -107,15 +109,15 @@ def main() -> None:\\n     taiwan_hub = (ROOT / \"site\" / \"topics\" / \"taiwan-aviation\" /\\n                   \"index.html\").read_text(encoding=\"utf-8\")\\n     assert \"<h1>臺灣航空新聞</h1>\" in taiwan_hub\\n-    assert 'href=\"/avwire/news/' in taiwan_hub\\n+    assert 'href=\"/news/' in taiwan_hub\\n     for slug in build.TOPICS:\\n-        assert f'href=\"/avwire/topics/{slug}/\"' in article\\n+        assert f'href=\"/topics/{slug}/\"' in article\\n \\n     policy = (ROOT / \"site\" / \"editorial-policy\" / \"index.html\").read_text(\\n         encoding=\"utf-8\")\\n     assert \"編輯、更正與聯絡政策\" in policy\\n     assert \"avwire/issues/new\" in policy\\n-    assert 'href=\"/avwire/editorial-policy/\"' in article\\n+    assert 'href=\"/editorial-policy/\"' in article\\n \\n     print(\"test_seo: OK\")\\n "
}
