The table was thirty languages typed by hand. It is now generated:
utils/generate_languages.py takes the 184 alpha-2 codes from pycountry,
the display names from CLDR through babel, and the writing direction from
CLDR character order. 159 languages carry their endonym; the remaining 25
have no CLDR entry and carry their English ISO name.
That corrects the right-to-left set, which had four entries and needs ten
— dv, ks, ps, sd, ug and yi were simply missed.
Only 29 languages ship an interface catalogue, so the other 155 render in
English until one is filled. make i18n-ui fills app/i18n/ui/ for them, and
make i18n now covers the interface strings as well; neither asks for a
string a shipped catalogue already answers, so hand-written entries stay.
184 entries do not fit on a screen, so the language menu scrolls inside
itself. overscroll-behavior keeps the page behind it from moving once the
list reaches its end.
babel and pycountry are dev dependencies: the generator needs them, the
application does not.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
test-e2e drove Cypress through act, which fails to start where it cannot
resolve a host address for its artifact server. It now starts Flask and
Cypress in one shell via scripts/run-e2e.sh, so both share a network
namespace; the act path stays available as test-e2e-act.
The script guards the cases that made the old target lie: it aborts when
something already serves the port instead of testing that server, checks
that its own Flask is alive before trusting a response, pins Cypress to
the same origin Flask binds, and drops ELECTRON_RUN_AS_NODE, which VS Code
exports and which makes Cypress' bundled Electron reject its own flags.
30 YAML and 18 JavaScript files had no linter. yamllint runs correctness
rules only, because the repository predates it and its cosmetic findings
would be noise; key-duplicates is the one that earns its keep, since
PyYAML keeps the last of two identical keys without complaining. eslint
runs the recommended set and already found a dead getBoundingClientRect()
call in navigation.js. Both get a CI job so make lint and the workflows
stop diverging.
flask>=3.1 because app.config["TRUSTED_HOSTS"] arrived in 3.1 and an older
Flask accepts the key and ignores it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Probe-first asset resolution (regression fix)
---------------------------------------------
cache_manager.cache_file() returned either a relative cache path
(success) or None (failure). The previous app.py fallback
asset['cache'] = cached or asset['source'] mixed both types into
one field, which the template wrapped in url_for('static', ...)
regardless — producing broken
/static/https://file.infinito.nexus/.../logo.png URLs whenever the
source couldn't be downloaded.
- New app/utils/asset_resolver.py: HEAD-probes the URL (3 s
timeout, image/* content type). On hit, embed directly via a
new external_url field — no download required. On miss, fall
back to cache_manager.cache_file. If that also fails, expose
the source URL via external_url so the browser shows the alt
text instead of an empty src.
- app.py exposes an asset_src(asset) context processor that
picks external_url first, then url_for('static', cache),
so the template never wraps an absolute URL in a static prefix.
- Templates (base, navigation, card) switch to asset_src(...) and
gate the card image branch on cache or external_url.
- 16 unit tests cover every probe/cache/fallback branch; one live
integration test exercises the canonical
https://file.infinito.nexus/assets/img/logo.png to prove the
probe-first path works end-to-end (cache dir stays empty).
- config.sample.yaml: new Infinito.Nexus card driven by the same
canonical asset URL.
Single source of truth for IMAGE_NAME and PORT
----------------------------------------------
- env.example is now the only place the literal values live.
- Makefile and docker-compose.yml reference \$(IMAGE_NAME) /
\${IMAGE_NAME:?…} (same for PORT); no defaults, no silent
fallbacks.
- New make env / make config bootstrap .env / app/config.yaml
from their checked-in templates. Idempotent.
- All container-using targets depend on the two bootstrap targets
so a fresh checkout runs in a single invocation.
- Recipes source .env at recipe-execution time so they pick up a
freshly bootstrapped .env in the same make invocation.
README
------
- Screenshot added under the title.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the host-side `make npm-install` step with a compose-level
`command:` override that runs `npm install` inside the container on
every start. Back node_modules and static/vendor with named volumes so
the bind-mounted source tree no longer shadows the install while state
still survives container restarts. Drop the now-redundant npm-install
target and its references in up/dev/prod/test-e2e.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add npm-install dependency to up/dev/prod targets so app/static/vendor
and app/node_modules exist on the host before the ./app:/app bind mount
masks the build-time artifacts inside the container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add AGENTS.md: require make test before every non-doc commit and
document the npm vendor asset workflow
- Add CLAUDE.md: instruct agents to read AGENTS.md at conversation start
- Add npm-install dependency to test-e2e Makefile target
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace requirements.txt with pyproject.toml for modern Python packaging
- Add unit, integration, lint and security test suites under tests/
- Add utils/export_runtime_requirements.py and utils/check_hadolint_sarif.py
- Split monolithic CI into reusable lint.yml, security.yml and tests.yml
- Refactor ci.yml to orchestrate reusable workflows; publish on semver tag only
- Modernize Dockerfile: pin python:3.12-slim, install via pyproject.toml
- Expand Makefile with lint, security, test and CI targets
- Add test-e2e via act with portfolio container stop/start around run
- Fix navbar_logo_visibility.spec.js: win.fullscreen() → win.enterFullscreen()
- Set use_reloader=False in app.run() to prevent double-start in CI
- Add app/core.* and build artifacts to .gitignore
- Fix apt-get → sudo apt-get in tests.yml e2e job
- Fix pip install --ignore-installed to handle stale act cache
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>