Details tab card layout ----------------------- The Details tab sat on .content-grid, a two-column CSS grid. Grid packs items into implicit rows, so every row is as tall as its tallest card and align-items:start leaves the shorter one with dead space beneath it: Warranty is short, Endpoint Status is tall, and Policy had to wait for the taller of the two rather than sliding up. That read as a staggered layout. Switched that one tab to multicol via a .details-flow modifier, which has no rows at all - each card starts where the previous one ended, so the holes close while cards still size to their content. Multicol is deliberately NOT applied to .content-grid globally. The note by that rule in style.css records why it was abandoned there: a column-span:all child (.pap-span, used by table panels) overlaps the shorter column when the two are unequal. That cannot happen on this tab - every panel in the details group is a keyvalue or list render, and spansFullWidth() only returns true for table (or a tabs panel holding one), so nothing in the subtree spans. Monitors and Service/Process DO carry table panels and stay on grid. The modifier is scoped to PCDetail, so the nine other detail views on .content-grid are untouched. Chosen over JS masonry on purpose. Masonry packs perfectly but has to measure every card and re-measure on resize, on panel collapse and on every async panel load - and these panels fetch after paint, so it would mean continuous forced reflow on a page that already polls. This is pure CSS: no measurement, no observer, no runtime cost. The tradeoff accepted is that column bottoms can end at slightly different heights. Cards carry break-inside: avoid so none fragments across the column break, and the 25px spacing returns as a margin because multicol has no gap between stacked items the way grid does. IP Configuration card --------------------- Removed: the data has no purpose on the page anymore. Taken out at the root rather than hidden - the panel declaration, the /asset/<id>/ipconfig route and the _IPCONFIG_COLUMNS constant all go, since a route with no consumer is dead surface that still answers requests. test_the_two_cards_do_not_duplicate_columns_beyond_the_address existed only to police the history/config column split; with one card gone it guards nothing, so it and its import go with it. The panel-count assertion in test_endpoints_asset_panels_are_computer_scoped moves 15 -> 14, which is the test doing its job and catching the removal. IP History is unchanged and still carries the address list. Not addressed here: three tzset() tests in test_endpoints_plugin.py fail on Windows because time.tzset() is Unix-only. Verified pre-existing against unmodified code; they pass in the Linux container.
5.1 KiB
Copilot instructions for shopdb-flask
Follow these when suggesting code. They are enforced by a naming/style hook and
by CI (.github/workflows/ci.yml) - suggestions that break them fail the build.
CONTRIBUTING.md is the full authority; this is the short version.
General Guidelines
- Finish the in-flight task before starting new workstreams.
- When asked to "take notes" on a future idea, capture it in the notes file only - do not implement it, and do not expand on it at length in replies.
- Use a temp file for multi-line git commit messages instead of heredoc syntax:
git commit -F <file>. - Avoid using
powershell.exe -Filefor launching scripts, as it fails if the script file is missing or quarantined.
Naming (LOCKED - the hook rejects violations)
- DB tables: lowercase, concatenated, plural. No underscores, no dashes.
machines,networkdevices,businessunits- NOTmachine_types,BusinessUnits. - DB columns: lowercase, concatenated, singular. No underscores.
machineid,lastzabbixsync,isactive- NOTmachine_id,last_zabbix_sync. - Foreign keys: referenced table (singular) +
id:locationid,vendorid. - Booleans:
is/hasprefix:isactive,isshopfloor. - Index names:
idx_<table>_<column>(underscores allowed here only).
Python
- A variable, attribute, function, or dict key that holds a DB value MUST match
the column name exactly - do NOT convert to snake_case.
Column
machineid->Machine.machineid,{"machineid": 1}, localmachineid. - Pure code that does NOT mirror a DB field uses normal snake_case (PEP 8):
loop_count,current_user,validate_input(). - Classes: PascalCase, spelled out (
NetworkDevice,AssetType).
JavaScript / Vue
- A JS variable holding an API field value matches the API key exactly - do NOT
camelCase it. API
{"machineid": 1}->response.machineid, nevermachineId. - Components: PascalCase (
AssetDetail.vue). CSS classes: lowercase-with-dashes.
API
- Endpoints: lowercase plural nouns, no underscores/dashes:
/api/networkdevices. - Query params + response keys match column names:
?locationid=5,{"machineid": 1, "lastzabbixsync": "..."}.
Allowed acronyms only
Universal: id url api http https json jwt sql os ip dns csv pdf cors ttl uuid html css orm. Domain: cmm cnc pc usb vnc winrm ssh ssl tls tcp udp smtp ldap vlan sso dnc focas clm mtconnect. Anything else: spell it out.
Banned shorthand
Never use cfg ctx mgr req res env util helper or db as a standalone variable
name. Spell out: config context manager request response environment utilities.
_bp is fine only as a suffix with a meaningful prefix (printers_bp).
Style (ASCII only)
- NO emojis anywhere - code, comments, strings, UI.
- NO em-dashes, en-dashes, Unicode arrows, or smart quotes. Plain ASCII only.
- Comments default to NONE. Add one only when the WHY is non-obvious; keep inline
#///comments terse. Docstrings stay normal English. - Dark theme is the default; keep UI functional and professional.
Architecture (do not violate)
- Plugins are the product. Plugin code lives in
plugins/<name>/{models,api,services,schemas}/with amanifest.json(single source of truth: name, version, dependencies, api_prefix) and aBasePluginsubclass inplugin.py. - Plugins never import core internals. Use the contract surface
shopdb.api(e.g.from shopdb.api import db, Asset, success_response). Adding to that surface is a contract-version bump + adocs/PLUGIN-HOOKSupdate in the same PR. - Migrations, never
db.create_all(). The core Alembic chain is inmigrations/versions/; each plugin owns its own chain underplugins/<name>/migrations/. New schema = a new migration with an idempotent guard and a real downgrade. Migrations must run clean on strict MySQL 8. Any model column/table change ships WITH its migration in the SAME change, and bringing up a new image MUST runflask db upgradethenflask plugin upgrade-allbefore serving traffic. A model that references a column its migration has not applied yet 500s every query on that table (1054 Unknown column), so code and schema must never be deployed out of step. - Asset model is the platform contract. Physical things are an
Assetplus a plugin subtype row linked byassetid(FK,ON DELETE CASCADE). Consumables with quantities are standalone tables, not assets. - Ledger pattern: a cached
quantityonhandmoves in the SAME commit as the signed transaction row it reflects. - Configuration for endpoint agent: The endpoint agent's live collector key and base URL are provisioned in the Windows registry
HKLM\SOFTWARE\GE\ShopDB(valuesCollectorKey,BaseUrl), not committed inappsettings.json;appsettings.jsonholds only non-secret runtime settings.
Before you finish a change
Run the three gates (CI runs the same):
python -m pytest tests/ -q
cd frontend && npx vitest run && npm run build && cd ..
bash scripts/check-naming-and-style.sh
Commits: short present-tense subject, body says WHY. No AI/tool attribution in commit messages, code comments, or docs.