Skip to content

Commit 1e26e10

Browse files
fix: force dashboard asset MIME types
Fix Windows live dashboard asset loading by forcing safe MIME types for JavaScript, CSS, JSON, and SVG assets in the localhost server. Fixes #66. Validation: GitHub Actions CI passed, plus local release gate and package smoke tests passed before PR.
1 parent 0091548 commit 1e26e10

9 files changed

Lines changed: 113 additions & 21 deletions

File tree

.codex-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codex-usage-tracker",
3-
"version": "0.11.2",
3+
"version": "0.11.3",
44
"description": "Unofficial local tracker for aggregate Codex token usage from local session logs.",
55
"author": {
66
"name": "Douglas Monsky"

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Unreleased
44

5+
## 0.11.3 - 2026-06-23
6+
7+
- Fix Windows `serve-dashboard` asset loading by forcing JavaScript, CSS, JSON, and SVG MIME types in the localhost server instead of trusting OS registry MIME mappings.
8+
59
## 0.11.2 - 2026-06-23
610

711
- Fix served dashboard shell hydration so `serve-dashboard --no-refresh` reliably populates the calls table from `/api/usage` when the initial HTML contains zero rows but indexed rows are available.

docs/development.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ fix/<issue-number>-short-description
3838
docs/<issue-number>-short-description
3939
chore/<issue-number>-short-description
4040
test/<issue-number>-short-description
41-
release/0.11.2
41+
release/0.11.3
4242
hotfix/0.3.3
4343
```
4444

@@ -91,7 +91,7 @@ blocked
9191
Recommended milestones:
9292

9393
```text
94-
0.11.2
94+
0.11.3
9595
1.0-readiness
9696
1.0.0
9797
```
@@ -146,8 +146,8 @@ python scripts/smoke_installed_package.py --docker
146146
To verify the public PyPI package instead of the local checkout:
147147

148148
```bash
149-
python scripts/smoke_installed_package.py --from-pypi --version 0.11.2
150-
python scripts/smoke_installed_package.py --docker --from-pypi --version 0.11.2
149+
python scripts/smoke_installed_package.py --from-pypi --version 0.11.3
150+
python scripts/smoke_installed_package.py --docker --from-pypi --version 0.11.3
151151
```
152152

153153
`scripts/check_release.py` treats these public-package smoke commands as release-state claims. Keep their `--version` and `codex-usage-tracking==...` values aligned with `pyproject.toml`; the release gate fails when the docs claim a different public version. It also checks that install docs point at the real PyPI distribution, `codex-usage-tracking`, and keep the warning that `codex-usage-tracker` is a different PyPI package.
@@ -294,8 +294,8 @@ After the release branch merges, tag from updated `main`, not from an unreviewed
294294
```bash
295295
git switch main
296296
git pull --ff-only
297-
git tag -a v0.11.2 -m "codex-usage-tracker 0.11.2"
298-
git push origin v0.11.2
297+
git tag -a v0.11.3 -m "codex-usage-tracker 0.11.3"
298+
git push origin v0.11.3
299299
```
300300

301301
Do not create or push release tags without maintainer approval.
@@ -304,7 +304,7 @@ Do not create or push release tags without maintainer approval.
304304

305305
Publishing uses GitHub Actions Trusted Publishing through `.github/workflows/publish.yml`; do not upload from a local machine and do not add PyPI or TestPyPI API tokens.
306306

307-
The first public package release, `0.3.0`, was published on June 8, 2026. Patch release `0.3.1` followed the same day to ship the live-dashboard skill launch fix. Patch release `0.3.2` made dashboard launch refresh the default and added runtime enablement for context loading. Minor release `0.4.0` added Python 3.14 support, release recovery docs, stricter privacy/support-bundle regression coverage, and large-history benchmark thresholds. Patch release `0.4.1` was published by workflow dispatch from `main`; it hardened the PyPI publish workflow and checked off completed 1.0 readiness gates. Minor release `0.5.0` added dashboard localization support and initial language catalogs. Minor release `0.6.0` is the performance and call-drilldown release with SQL-backed live API slices, materialized thread summaries, faster evidence loading, and dashboard runtime module refactors. Patch release `0.6.1` aligns the final README/package screenshots and companion plugin assets. Minor release `0.7.0` adds observed usage snapshots and the latest-observed dashboard card while keeping raw evidence on demand only. Minor release `0.8.0` adds aggregate diagnostics, source-offset context seeking, and live dashboard loading hardening. Patch release `0.8.1` improves Diagnostics fact table readability with pinned fact names and sortable fact columns. Minor release `0.9.0` adds persisted diagnostic snapshots, on-demand diagnostic refresh, tool/command/file-read/concentration reports, and Diagnostics dashboard panels. Minor release `0.10.0` adds Git/GitHub CLI diagnostics, file-modification diagnostics, and derived call timing fields across the dashboard and API. Patch release `0.10.1` adds lightweight action timing to context evidence and ships the synthetic Git Interactions README screenshot. Minor release `0.11.0` adds usage-drain diagnostic reports, projected weekly credit charts, and cumulative thread cost curves. Patch release `0.11.1` improves the Diagnostics weekly projection table and stale snapshot reload controls. Patch release `0.11.2` hardens the synthetic source-log benchmark smoke test and improves benchmark failure diagnostics.
307+
The first public package release, `0.3.0`, was published on June 8, 2026. Patch release `0.3.1` followed the same day to ship the live-dashboard skill launch fix. Patch release `0.3.2` made dashboard launch refresh the default and added runtime enablement for context loading. Minor release `0.4.0` added Python 3.14 support, release recovery docs, stricter privacy/support-bundle regression coverage, and large-history benchmark thresholds. Patch release `0.4.1` was published by workflow dispatch from `main`; it hardened the PyPI publish workflow and checked off completed 1.0 readiness gates. Minor release `0.5.0` added dashboard localization support and initial language catalogs. Minor release `0.6.0` is the performance and call-drilldown release with SQL-backed live API slices, materialized thread summaries, faster evidence loading, and dashboard runtime module refactors. Patch release `0.6.1` aligns the final README/package screenshots and companion plugin assets. Minor release `0.7.0` adds observed usage snapshots and the latest-observed dashboard card while keeping raw evidence on demand only. Minor release `0.8.0` adds aggregate diagnostics, source-offset context seeking, and live dashboard loading hardening. Patch release `0.8.1` improves Diagnostics fact table readability with pinned fact names and sortable fact columns. Minor release `0.9.0` adds persisted diagnostic snapshots, on-demand diagnostic refresh, tool/command/file-read/concentration reports, and Diagnostics dashboard panels. Minor release `0.10.0` adds Git/GitHub CLI diagnostics, file-modification diagnostics, and derived call timing fields across the dashboard and API. Patch release `0.10.1` adds lightweight action timing to context evidence and ships the synthetic Git Interactions README screenshot. Minor release `0.11.0` adds usage-drain diagnostic reports, projected weekly credit charts, and cumulative thread cost curves. Patch release `0.11.1` improves the Diagnostics weekly projection table and stale snapshot reload controls. Patch release `0.11.2` fixes served dashboard shell hydration and hardens the synthetic source-log benchmark smoke test. Patch release `0.11.3` fixes Windows live dashboard asset MIME handling.
308308

309309
- GitHub Release: `https://github.com/douglasmonsky/codex-usage-tracker/releases/tag/v0.3.0`
310310
- GitHub Release: `https://github.com/douglasmonsky/codex-usage-tracker/releases/tag/v0.3.1`

docs/one-dot-oh-readiness.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@ Not guaranteed:
2424

2525
## 1. Public Install And Package Metadata
2626

27-
- [x] Verify the current public PyPI version is visible as `0.11.2`: `python -c "import json, urllib.request; print(json.load(urllib.request.urlopen('https://pypi.org/pypi/codex-usage-tracking/json'))['info']['version'])"`.
28-
- [x] Verify public venv install for `0.11.2`: `python -m venv /tmp/codex-usage-pypi-smoke && . /tmp/codex-usage-pypi-smoke/bin/activate && python -m pip install codex-usage-tracking==0.11.2 && codex-usage-tracker --version`.
29-
- [x] Verify public pipx install path for `0.11.2`: `PIPX_HOME=/tmp/codex-usage-pipx-home PIPX_BIN_DIR=/tmp/codex-usage-pipx-bin pipx install codex-usage-tracking==0.11.2 && /tmp/codex-usage-pipx-bin/codex-usage-tracker --version`.
27+
- [x] Verify the current public PyPI version is visible as `0.11.3`: `python -c "import json, urllib.request; print(json.load(urllib.request.urlopen('https://pypi.org/pypi/codex-usage-tracking/json'))['info']['version'])"`.
28+
- [x] Verify public venv install for `0.11.3`: `python -m venv /tmp/codex-usage-pypi-smoke && . /tmp/codex-usage-pypi-smoke/bin/activate && python -m pip install codex-usage-tracking==0.11.3 && codex-usage-tracker --version`.
29+
- [x] Verify public pipx install path for `0.11.3`: `PIPX_HOME=/tmp/codex-usage-pipx-home PIPX_BIN_DIR=/tmp/codex-usage-pipx-bin pipx install codex-usage-tracking==0.11.3 && /tmp/codex-usage-pipx-bin/codex-usage-tracker --version`.
3030
- [x] Verify installed package resources from a built wheel: `python scripts/smoke_installed_package.py`.
3131
- [x] Verify installed package resources in Linux Docker: `python scripts/smoke_installed_package.py --docker`.
32-
- [x] Verify public PyPI package in Docker: `python scripts/smoke_installed_package.py --docker --from-pypi --version 0.11.2`.
32+
- [x] Verify public PyPI package in Docker: `python scripts/smoke_installed_package.py --docker --from-pypi --version 0.11.3`.
3333
- [x] Verify PyPI metadata names remain unchanged: `python scripts/check_release.py`.
3434
- [x] Add Python 3.14 as an official support target after CI, package classifiers, docs, and installed-package smoke coverage were added. Docker smoke coverage uses `python:3.14-slim` by default. Track this in issue #12.
3535

@@ -134,14 +134,14 @@ Not guaranteed:
134134

135135
## Evidence References
136136

137-
These references are the concrete proof behind completed checklist items. Public package smoke commands are version-specific to `0.11.2`; all repo tests use synthetic or aggregate-only data.
137+
These references are the concrete proof behind completed checklist items. Public package smoke commands are version-specific to `0.11.3`; all repo tests use synthetic or aggregate-only data.
138138

139139
### Public Install And Package Metadata
140140

141141
- Public PyPI version, public venv install, and public pipx install are proven by the exact public-install commands in section 1.
142142
- Built-wheel and installed-resource coverage is proven by `scripts/smoke_installed_package.py` and `tests/test_cli_release.py::test_installed_package_smoke_checks_help_for_stable_commands`.
143143
- Linux package-resource coverage is proven by `scripts/smoke_installed_package.py --docker`.
144-
- Public PyPI Docker coverage is proven by `scripts/smoke_installed_package.py --docker --from-pypi --version 0.11.2`.
144+
- Public PyPI Docker coverage is proven by `scripts/smoke_installed_package.py --docker --from-pypi --version 0.11.3`.
145145
- PyPI metadata, package/distribution names, package resources, source/wheel member names, Python 3.10-3.14 support metadata, CI workflow requirements, publish workflow safety text, and tracked secret patterns are proven by `scripts/check_release.py`, `scripts/check_release.py --dist`, and `tests/test_cli_release.py::test_release_check_script_passes`.
146146

147147
### Upgrade And Migration
@@ -219,8 +219,8 @@ These references are the concrete proof behind completed checklist items. Public
219219
- Publish workflow package name, Trusted Publishing, TestPyPI/PyPI job presence, event guards, no push/PR publishing, no token/password publishing, and manual PyPI main/tag preflight are proven by `scripts/check_release.py::_check_publish_workflow`.
220220
- The GitHub `pypi` environment gate is proven by `gh api repos/douglasmonsky/codex-usage-tracker/environments/pypi`, which reports a `required_reviewers` protection rule and `can_admins_bypass=false`.
221221
- Dist filename and wheel/sdist member checks are proven by `python -m build`, `python -m twine check dist/*`, and `python scripts/check_release.py --dist`.
222-
- TestPyPI publish process is proven by a workflow-dispatch run on `main`, followed by TestPyPI metadata and clean virtualenv install checks for `codex-usage-tracking==0.11.2`.
223-
- PyPI publish process is proven by a workflow-dispatch run on `main`, protected `pypi` environment approval, PyPI metadata visibility, clean virtualenv install, temporary pipx install, and Docker public-package smoke for `codex-usage-tracking==0.11.2`.
222+
- TestPyPI publish process is proven by a workflow-dispatch run on `main`, followed by TestPyPI metadata and clean virtualenv install checks for `codex-usage-tracking==0.11.3`.
223+
- PyPI publish process is proven by a workflow-dispatch run on `main`, protected `pypi` environment approval, PyPI metadata visibility, clean virtualenv install, temporary pipx install, and Docker public-package smoke for `codex-usage-tracking==0.11.3`.
224224
- Release recovery documentation is proven by `scripts/check_release.py` required-file and docs checks.
225225

226226
### Known Limitations

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "codex-usage-tracking"
7-
version = "0.11.2"
7+
version = "0.11.3"
88
description = "Unofficial local Codex plugin and dashboard for investigating aggregate token usage, costs, caching, and thread patterns."
99
readme = "README.md"
1010
requires-python = ">=3.10"

skills/codex-usage-tracker/scripts/run_mcp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616
PACKAGE_SPEC = os.environ.get(
1717
"CODEX_USAGE_TRACKER_PACKAGE_SPEC",
18-
"codex-usage-tracking==0.11.2",
18+
"codex-usage-tracking==0.11.3",
1919
)
20-
RUNTIME_VERSION = "0.11.2"
20+
RUNTIME_VERSION = "0.11.3"
2121
PACKAGE_SPEC_MARKER = ".codex-usage-tracker-package-spec"
2222
MODULE_CHECK = (
2323
"import importlib.metadata; "

src/codex_usage_tracker/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
from codex_usage_tracker.models import UsageEvent
44

5-
__version__ = "0.11.2"
5+
__version__ = "0.11.3"
66

77
__all__ = ["UsageEvent", "__version__"]

src/codex_usage_tracker/server.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@
109109
_validate_context_api_mode = server_utils.validate_context_api_mode
110110
_validate_loopback_host = server_utils.validate_loopback_host
111111

112+
_DASHBOARD_ASSET_MIME_TYPES = {
113+
".css": "text/css; charset=utf-8",
114+
".js": "text/javascript; charset=utf-8",
115+
".json": "application/json; charset=utf-8",
116+
".svg": "image/svg+xml",
117+
}
118+
112119

113120
def _optional_int_query(params: dict[str, list[str]], key: str) -> int | None:
114121
value = _first(params.get(key))
@@ -404,6 +411,12 @@ def end_headers(self) -> None:
404411
)
405412
super().end_headers()
406413

414+
def guess_type(self, path: str) -> str:
415+
forced_type = _DASHBOARD_ASSET_MIME_TYPES.get(Path(path).suffix.lower())
416+
if forced_type is not None:
417+
return forced_type
418+
return super().guess_type(path)
419+
407420
def _is_dashboard_html_request(self) -> bool:
408421
path = urlparse(self.path).path
409422
return path in {"/", f"/{self._dashboard_name}"}

tests/test_dashboard_server.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
import urllib.parse
88
import urllib.request
99
from functools import partial
10-
from http.server import ThreadingHTTPServer
10+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
1111
from pathlib import Path
1212

13+
import pytest
1314
from store_dashboard_helpers import (
1415
SESSION_ID,
1516
_assert_contract,
@@ -31,6 +32,80 @@
3132
)
3233

3334

35+
def test_dashboard_server_forces_dashboard_asset_mime_types(
36+
tmp_path: Path,
37+
monkeypatch: pytest.MonkeyPatch,
38+
) -> None:
39+
from codex_usage_tracker.server import _UsageDashboardHandler
40+
41+
def registry_text_plain_guess_type(self: SimpleHTTPRequestHandler, path: str) -> str:
42+
suffix = Path(path).suffix.lower()
43+
if suffix in {".css", ".js", ".json"}:
44+
return "text/plain"
45+
return "application/octet-stream"
46+
47+
monkeypatch.setattr(
48+
SimpleHTTPRequestHandler,
49+
"guess_type",
50+
registry_text_plain_guess_type,
51+
)
52+
53+
asset_dir = tmp_path / "codex-usage-tracker-assets"
54+
locale_dir = asset_dir / "locales"
55+
locale_dir.mkdir(parents=True)
56+
(asset_dir / "dashboard.js").write_text("window.__dashboardLoaded = true;\n", encoding="utf-8")
57+
(asset_dir / "dashboard.css").write_text("body { color: black; }\n", encoding="utf-8")
58+
(locale_dir / "en.json").write_text('{"dashboard": "Usage"}\n', encoding="utf-8")
59+
60+
handler = partial(
61+
_UsageDashboardHandler,
62+
directory=str(tmp_path),
63+
db_path=tmp_path / "usage.sqlite3",
64+
pricing_path=tmp_path / "pricing.json",
65+
allowance_path=tmp_path / "allowance.json",
66+
thresholds_path=tmp_path / "thresholds.json",
67+
projects_path=tmp_path / "projects.json",
68+
limit=5000,
69+
since=None,
70+
codex_home=tmp_path / ".codex",
71+
include_archived=False,
72+
dashboard_name="dashboard.html",
73+
context_chars=2000,
74+
api_token="test-token",
75+
context_api_enabled=True,
76+
refresh_lock=threading.Lock(),
77+
)
78+
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
79+
thread = threading.Thread(target=server.serve_forever, daemon=True)
80+
thread.start()
81+
responses = []
82+
try:
83+
base_url = f"http://127.0.0.1:{server.server_port}/codex-usage-tracker-assets"
84+
expected_assets = {
85+
"dashboard.js": "text/javascript",
86+
"dashboard.css": "text/css",
87+
"locales/en.json": "application/json",
88+
}
89+
for asset_path, expected_content_type in expected_assets.items():
90+
with urllib.request.urlopen( # noqa: S310 - local test server only
91+
f"{base_url}/{asset_path}",
92+
timeout=5,
93+
) as response:
94+
content_type = response.headers.get("Content-Type")
95+
nosniff = response.headers.get("X-Content-Type-Options")
96+
responses.append((asset_path, expected_content_type, content_type, nosniff))
97+
finally:
98+
server.shutdown()
99+
server.server_close()
100+
thread.join(timeout=5)
101+
102+
assert len(responses) == len(expected_assets)
103+
for asset_path, expected_content_type, content_type, nosniff in responses:
104+
assert content_type is not None, asset_path
105+
assert content_type.split(";", 1)[0] == expected_content_type
106+
assert nosniff == "nosniff"
107+
108+
34109
def test_dashboard_server_usage_api_refreshes_aggregate_rows(tmp_path: Path) -> None:
35110
from codex_usage_tracker.server import _UsageDashboardHandler
36111

0 commit comments

Comments
 (0)