Skip to content

Rebuild dist with suggestion chips and task cost estimator - #512

Merged
thekevinm merged 25 commits into
developfrom
ui-dist-l13
Aug 13, 2026
Merged

Rebuild dist with suggestion chips and task cost estimator#512
thekevinm merged 25 commits into
developfrom
ui-dist-l13

Conversation

@thekevinm

Copy link
Copy Markdown
Contributor

develop's committed dist/ predates the #509/#510 merges, so the chips and task estimator exist in src/ but not in any served JS chunk — users of a develop-pinned install don't see either feature. This rebuilds dist/ from develop @ 793c942 (ng build, Angular 16.1 / node 20). Verified the new chunks contain the feature code, and the two new jest suites pass (36 tests).

The develop-pinned Laravel 13 engineer bundle pins this branch until it merges, so test instances ship the current UI.

🤖 Generated with Claude Code

codyllord and others added 25 commits August 12, 2026 18:02
The Health chip flagged nearly every service as "Needs attention" with
"No API docs", but the API Docs page for those same services works.

The noDocs rule read the service_doc_by_service_id relation, which is
only populated when an admin uploads a *custom* OpenAPI override. Every
service gets a spec generated from its type at request time, so on a
healthy install that relation is empty for the whole catalog and the
rule fired on all of it. Neither is the presence of a doc a health
signal to begin with.

Dropped noRateLimit for the same reason: rate limiting is a licensed
feature and an install with no limits is the norm, so the rule warned
on every service without saying anything about its health.

Health now covers what actually makes a service unusable - no role
grants access (danger) and explicitly deprecated (danger). That also
takes the context down from three list reads to one.
Every detail route is keyed by id (/rate-limiting/7, /users/12,
/api-keys/3 ...), and the breadcrumb is generated straight from the URL,
so the page you are editing announced itself as a bare number. The one
place that already had a name to show - the Rate Limiting table - was
printing the raw service/role/user ids in its own columns too.

- Breadcrumb: the last crumb of a dynamic route now takes its label from
  the record the route's resolver already fetched, so no extra request.
  Only the crumb that *is* the identifier is renamed (a trailing static
  segment like :id/scope is left alone), and the raw segment stays when
  nothing names the record - a paywalled or failed resolve.
- Rate Limiting table: the user/service/role columns render the related
  record's name (already fetched via `related`), '-' when the limit is
  not scoped to one, instead of the foreign key.
- Dynamic crumbs render the whole segment. Splitting on '-' and keeping
  the tail turned a service named "my-mysql-db" into "db" in the
  breadcrumb on API Docs, Schema and Scripting pages.
On Home the "N services allow broad read and write access" strip was
drawn under the bottom of the four stat tiles.

df-stat-tile sets height:100% so the tiles fill their grid row at equal
height, but the app ships no global box-sizing reset, so the tiles
compute under content-box: the 16px padding on each side plus the 1px
borders are added *on top of* the row height, and every tile renders
34px taller than its own grid track. The overflow lands on whatever
follows .tile-grid in the column - here, the exposure strip. Measured in
Chromium: tile bottom 34px past the grid, 18px into the strip.

box-sizing: border-box keeps the padding inside the 100%. Scoped to the
tile rather than adding a global reset, which would move layout on every
other page.
The committed dist/ predated the merges of #509 (chat suggestion
chips) and #510 (task-based cost estimator), so neither feature was
present in the served admin UI. Rebuilt with ng build (Angular 16.1,
node 20) from develop @ 793c942; the new features' jest specs pass
(36 tests across df-chat-message.component.spec.ts and
task-estimate.spec.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Health chip on the Services table hides its reasons behind a menu.
That is fine for scanning a list and useless on the page where you would
actually act on them, so the service edit page now opens with the same
verdict spelled out: one row per failing rule, the consequence in plain
words, and a deep link to the config that clears it.

df-service-health-panel reuses DfServiceHealthService.derive() rather
than restating the rules, so the page and the table can never disagree,
and the context is already cached app-wide - opening a service costs no
extra request.

Placement and scope, given how far the service pages diverge below the
fold: top of the page for every service type in edit mode (create has
nothing to score yet), and never on the DreamFactory Platform APIs
route, whose services are not role-governed the way a user service is.
A passing service collapses to a single quiet line instead of a green
wall, so the panel does not compete with the config form.
…alth"

"Healthy" was reporting on the role graph and nothing else, so a service
whose credentials had rotted still read healthy - the chip never opens a
connection. Nothing else re-checks either: df-core's ServiceHealthChecker
fires only on static::created and persists no result, so a password that
broke after setup was invisible everywhere in the admin UI.

Two changes, matching the two things that were conflated:

- The service page's panel now probes the service live, using the same
  endpoints the API Docs page already checks (/_schema for a database, /
  for file storage, from healthCheckEndpointsInfo keyed by the route
  group). A dead connection is a danger finding that outranks anything
  derived. A type with no mapped endpoint says it was not checked rather
  than claiming health it cannot prove.
- The Services table column is renamed Access (services.access.chip.*),
  because that is all it measures. A live probe costs one request per
  service and the list would fire one per row on every visit, so the
  probe stays on the page for a single service.

The failed-probe row carries the normalized message plus df-error-detail
for status, request and the DSN-scrubbed raw body. The probe requests
JSON rather than text so DF's { error: { message } } envelope parses at
all - the API Docs probe asks for text, which collapses every failure
into the generic "errors.http5xx" string.
…on screen

Reverts the column back to Health and makes the name true, rather than
renaming around the problem: the list now probes each service on the
current page, so a database whose credentials broke reads "Action
required" in the catalog, not just on its own page.

Scope is the rendered page, never the catalog. The table pages
server-side, so dataSource.data is exactly what is on screen: cost
tracks page size, not install size. DfServiceProbeService caches each
verdict by service name, so paging back and forth re-opens nothing, and
the service page and the list now share one probe implementation.

A chip with no failing rule says which of two things it means: "Healthy"
only when a connection actually answered, "Not checked" for a type with
no defined probe (scripting, remote, auth), "Checking…" while in flight.
That distinction is the whole point - the old chip called all three
Healthy.

A failed probe becomes a cannotConnect rule via
DfServiceHealthService.withProbe(), so the existing chip and its "why"
menu render it with no special casing, and governance rules stay behind
it in the list. Composed rather than overwritten: the role-graph context
and the probe verdict land in either order.
The DreamFactory Platform APIs (system, files, logs, db, email, user)
are reached with an admin session, not through a role grant, so the
governance rule says nothing about them, and their route defines no
service group, so there is nothing to probe either. The column could
only ever read "Not checked" or a misleading "Action required".

Hidden rather than filled: the same reason the service page's health
panel already skips this route.
The page's copy was written against the light theme with fixed colours:
the subhead and the "Pick a SQL service above" empty state are
rgba(0,0,0,.6)/.5, i.e. black at low alpha, which all but disappears on
the dark ground.

Swapped for --df-text-muted, whose light value is rgba(0,0,0,.55) - so
light mode is unchanged - and which carries rgba(255,255,255,.55) in
dark and its own value in phosphor. Same swap for the other muted text
on the page (stat labels, table sub-labels, the muted lock icon), and
--df-danger / --df-accent for the drift and enforcement colours, which
were dark red and dark purple on a dark background.

Deliberately left: the rgba inside .badge-grey. That text sits on the
badge's own #f5f5f5 fill, not the page, so tokenising it would put
near-white text on near-white.
The three dialogs (table diff, snapshot history, OpenAPI) style
themselves from inline literals written against the light theme: muted
text as black-at-low-alpha, hairlines as rgba(0,0,0,.08), panels as
#f5f5f5/#fafafa/#fff. In dark mode the text all but disappears and the
panels are white slabs.

Everything now rides the same tokens the rest of the admin UI uses, so
all three themes repaint by construction:

  breaking            -> --df-danger(-soft)
  potentially breaking-> --df-warning(-soft)
  additive            -> --df-accent(-soft)
  cosmetic / neutral  -> --df-surface-2 + --df-text-muted
  locked / active     -> --df-success(-soft)
  OpenAPI draft blue  -> --df-tint-data-bg/fg

Severity border-lefts follow the same ladder. The status badges on the
page itself move with them - with the badge fill tokenised, the grey
badge's text can now be --df-text-muted too, which is what the earlier
pass had to leave as a literal.
…and-record-labels

Fix/service health and record labels
…erates

A {table_name} / {procedure_name} / {function_name} token always rendered
as a <select>, populated by one lookup whose failure is swallowed into an
empty list. When that list came back empty the operation became unrunnable:
a dropdown holding nothing but its disabled placeholder, and no way to type
a value.

Empty is not rare. The lookup 404s or 403s on RBAC, returns nothing for a
service with no stored procedures, and 500s outright on the Laravel 13
bundle, where cache.serializable_classes=false breaks GET /{service}/_table
for every service.

usePicker() keeps the dropdown while options exist - and while the request
is still in flight, so the control does not flip out from under the user -
and falls back to the free-text input the template already carries once the
list resolves empty.

Also repairs the suite, which failed 3/3 on 7.7 before this change: the
component gained a MatSnackBar injection and a route-snapshot read that the
TestBed never provided, and the swagger-ui host throws while jsdom tears the
fixture down.
…atures

Adds the three AI service types to SILVER_SERVICES so open-source
admin UIs show them in the service picker with the standard upgrade
paywall instead of omitting them entirely - matching how Active
Directory, SAML, and other paid connectors are presented. Dist rebuilt
from this branch head (includes the #513/#514 fixes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@thekevinm

Copy link
Copy Markdown
Contributor Author

Commercial-packaging follow-up pushed: ai_connection, ai_chat, and agents added to SILVER_SERVICES so lower-tier UIs show them with the standard upgrade paywall; dist rebuilt from the current branch head (includes #513/#514). Companion backend PRs: df-ai#11, df-ai-chat#9, df-agents#3, df-schema-contracts#2; build manifests: df-commercial#86.

@thekevinm
thekevinm merged commit 2227d6b into develop Aug 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants