4.9 KiB
SmartTime 9 UI integration
Observed read-only against the internal instance on 2026-09-25 using a separate headless Playwright browser. No stamps, corrections or approvals were submitted. The dashboard and September month row both showed 09:29, not exactly 09:30.
Changes from 8.6
- Login redirects to
/login; the user input is nowinput[name="username"], notuseraccount. The password input remainsinput[name="password"]. - The application is Vaadin Flow with web components and shadow DOM, not the old
GWT dashboard. Setting
location.hash = 'dashboard_tab'is obsolete. - Sidebar items are
vaadin-side-nav-item[path="..."]. Dashboard has an empty path, month view hasMonthlyOV; the selected item has acurrentattribute. - The personal card still has a
Letzte Buchunglabel followed by an HH:MM label. It is inside.shadow-xs. Itsvaadin-icon[icon="far:building"]uses the status color. The phone icon is not the presence indicator. - There is no
Aktueller Statusvalue. The status legend contains all four labels regardless of actual presence. Matching the firstAnwesendin body text is unsafe. Match the personal building icon's computed fill to each legend swatch's computed background color; unknown or ambiguous colors fail closed.
Loading and recovery
Login, navigation and monthly selections finish asynchronously. Wait for the
expected DOM and the Vaadin Flow clients' isActive() queues to become idle.
Do not use networkidle: the app maintains background communication. Menu options
remain in the DOM after closing; locate visible top-level menu buttons separately.
smarttime_browser.py launches one isolated worker per read. A whole-job watchdog
also covers cases in which a Playwright DOM operation or browser shutdown hangs.
On Windows it terminates only that job's process tree (taskkill /PID <job> /T /F)
before retrying with a new browser. There is no browser profile lock or poisoned
thread queue to reuse after a failure. The old profile is left untouched on disk.
The subprocess boundary also resolves Streamlit's Windows SelectorEventLoop issue.
UI waits are bounded (normally 40 seconds); the hard job budgets are 75 seconds for status and 180 seconds for export, with at most three read attempts. Exhaustion is an unknown/error result, never proof that a booking failed.
Monthly extraction
- Click
vaadin-side-nav-item[path="MonthlyOV"]and wait forcurrent. - Read the visible month/year
vaadin-menu-bar-buttonlabels. Select the year and month by their exact menu-item names; wait for the new labels and idle server queue. - Read
vaadin-grid.Customers-grid. Its shadow root contains#header,#itemsand the scrolling#table. Each cell's<slot>resolves to light-DOM content viaassignedElements(). This preserves empty columns and multiple bookings. - Use the grid's
scrollToIndex()to render successive portions of the month. The grid'ssizemust equal the calendar's number of days. Collect by day, compare overlapping rows, and require every date before writing the export.
Observed columns, in order: Datum, an unlabeled notes/icon column,
Kommen / Gehen, Soll, Ist, TSaldo, Saldo, Pause, Abwesenheit.
The unlabeled column is retained in raw_cells. Booking strings are kept in
display order; absence credits are not turned into fictitious bookings.
CLI and diagnostics
scripts/smarttime_cli.py uses exactly the same reader/recovery code as the app.
--month YYYY-MM exports JSON; --artifacts DIRECTORY saves a screenshot and
sanitized HTML for the dashboard and multiple month scroll positions. The HTML
includes declarative shadow-root snapshots; ordinary page.content() omits them.
Input value attributes are stripped, but exported time data remains personal.
scripts/smarttime_probe.py is the separate interactive exploration tool. It uses
a fresh headless browser, accepts JSON commands on stdin, and saves local snapshots
under .local/smarttime/<timestamp>. Example commands:
{"action":"snapshot"}
{"action":"login","user":"input[name=username]","password":"input[name=password]"}
{"action":"selector","selector":"a[href=MonthlyOV]"}
{"action":"snapshot"}
{"action":"quit"}
The probe loads credentials from settings without printing them. Its generic selectors are intended for developer inspection; use only navigation/read controls. The supported CLI exposes only read operations.
Validation
Live dashboard reads, the app's verification API, the Streamlit Jetzt prüfen button, and full August/September 2026 and September 2025 exports were exercised. The Streamlit check used the live backend with the auto-stamp scheduler mocked out. Offline tests cover the real DOM-reading JavaScript, status colors, delayed rendering, unknown/invalid values, slot extraction, full calendar validation, and retry guards. A separate test launches real Chromium with an intercepted navigation that never responds, then verifies watchdog cleanup.