Files
shopdb-flask/.github/copilot-instructions.md
benjamin.baker ba38e4d39d PC Detail: flow Details cards, drop the IP Configuration card
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.
2026-09-05 15:27:50 -04:00

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 -File for 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 - NOT machine_types, BusinessUnits.
  • DB columns: lowercase, concatenated, singular. No underscores. machineid, lastzabbixsync, isactive - NOT machine_id, last_zabbix_sync.
  • Foreign keys: referenced table (singular) + id: locationid, vendorid.
  • Booleans: is/has prefix: 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}, local machineid.
  • 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, never machineId.
  • 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 a manifest.json (single source of truth: name, version, dependencies, api_prefix) and a BasePlugin subclass in plugin.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 + a docs/PLUGIN-HOOKS update in the same PR.
  • Migrations, never db.create_all(). The core Alembic chain is in migrations/versions/; each plugin owns its own chain under plugins/<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 run flask db upgrade then flask plugin upgrade-all before 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 Asset plus a plugin subtype row linked by assetid (FK, ON DELETE CASCADE). Consumables with quantities are standalone tables, not assets.
  • Ledger pattern: a cached quantityonhand moves 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 (values CollectorKey, BaseUrl), not committed in appsettings.json; appsettings.json holds 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.