diff --git a/examples/app_hub/hub.css b/examples/app_hub/hub.css index 48e5d08..d872c58 100644 --- a/examples/app_hub/hub.css +++ b/examples/app_hub/hub.css @@ -8,19 +8,19 @@ isolation: isolate; } -/* 97% transparent watermark */ +/* Debug watermark visibility */ .hub-page::before { content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; - background: url("assets/hub-logo.svg") center center / min(92vmin, 960px) no-repeat; - opacity: 0.03; + background: url("assets/hub-logo.svg") center center / cover no-repeat; + opacity: 0.75; } [data-theme="light"] .hub-page::before { - opacity: 0.035; + opacity: 0.75; } .hub-shell { diff --git a/examples/crash_reporter/backend/public/assets/css/app.css b/examples/crash_reporter/backend/public/assets/css/app.css index 71bbc0a..cb185f0 100644 --- a/examples/crash_reporter/backend/public/assets/css/app.css +++ b/examples/crash_reporter/backend/public/assets/css/app.css @@ -33,10 +33,12 @@ body { background: var(--bg); color: var(--text); min-height: 100vh; + display: flex; + flex-direction: column; } a { color: var(--accent); text-decoration: none; } a:hover { text-decoration: underline; } -.shell { display: flex; min-height: calc(100vh - 40px); } +.shell { display: flex; min-height: calc(100vh - 40px); flex: 1 0 auto; } .shell.single .main-pane { max-width: 1100px; margin: 0 auto; padding: 24px; } .nav-pane { width: 56px; @@ -404,9 +406,14 @@ a:hover { text-decoration: underline; } border-top: 1px solid var(--border); display: flex; align-items: center; + justify-content: space-between; padding: 0 16px; font-size: 12px; color: var(--muted); + position: sticky; + bottom: 0; + background: var(--surface); + z-index: 40; } .login-page { display: flex; diff --git a/examples/crash_reporter/backend/src/TicketRepository.php b/examples/crash_reporter/backend/src/TicketRepository.php index 5e3fb0d..5d878f3 100644 --- a/examples/crash_reporter/backend/src/TicketRepository.php +++ b/examples/crash_reporter/backend/src/TicketRepository.php @@ -11,6 +11,7 @@ final class TicketRepository { 'device_model', 'owner_username', ]; + private static ?array $ticketColumns = null; public static function insert(array $payload): void { $err = TicketTagCatalog::validate(is_array($payload['tags'] ?? null) ? $payload['tags'] : []); @@ -88,9 +89,16 @@ final class TicketRepository { $countStmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE $whereSql"); $countStmt->execute($params); $total = (int) $countStmt->fetchColumn(); - $sql = "SELECT id, ticket_id, title, brief, opened_at_ms, updated_at_ms, created_at_ms, - owner_username, verified_by_username, device_model, app_package, app_version, - os_name, os_version, rating, tags_json, workflow_state, assignees_json + $select = 'id, ticket_id, title, brief, opened_at_ms, updated_at_ms, created_at_ms, + owner_username, verified_by_username, device_model, app_package, app_version, + os_name, os_version, rating, tags_json'; + if (self::hasTicketColumn('workflow_state')) { + $select .= ', workflow_state'; + } + if (self::hasTicketColumn('assignees_json')) { + $select .= ', assignees_json'; + } + $sql = "SELECT $select FROM tickets WHERE $whereSql ORDER BY $sortCol $dir LIMIT $perPage OFFSET $offset"; $stmt = $pdo->prepare($sql); $stmt->execute($params); @@ -181,7 +189,7 @@ final class TicketRepository { $sets[] = 'verified_by_username = ?'; $params[] = $v !== '' ? mb_substr($v, 0, 64) : null; } - if (isset($fields['workflow_state'])) { + if (isset($fields['workflow_state']) && self::hasTicketColumn('workflow_state')) { $state = TicketWorkflow::normalize((string) $fields['workflow_state']); $sets[] = 'workflow_state = ?'; $params[] = $state; @@ -192,7 +200,7 @@ final class TicketRepository { $sets[] = 'tags_json = ?'; $params[] = json_encode($tags, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } - if (array_key_exists('assignees', $fields)) { + if (array_key_exists('assignees', $fields) && self::hasTicketColumn('assignees_json')) { $assignees = self::normalizeAssignees($fields['assignees']); $err = UserRepository::validateUsernames( array_map(static fn (array $a): string => $a['username'], $assignees) @@ -239,6 +247,13 @@ final class TicketRepository { } } + private static function hasTicketColumn(string $column): bool { + if (self::$ticketColumns === null) { + self::$ticketColumns = Database::columnNames(Database::pdo(), 'tickets'); + } + return in_array($column, self::$ticketColumns, true); + } + /** @param array $row */ private static function mapListRow(array $row): array { $tags = parse_report_tags($row['tags_json'] ?? '[]');