feat(t-paliad-134): B1 surface — render concept cards beneath decision tree
Pathway B B1 mode previously rendered an empty result area on every
state — the runB1Search() output target was #fristen-search-results,
which lives inside the B2 panel. When B2 is hidden (B1 active), the
results were written into a hidden subtree and never seen.
Changes:
- TSX: add #fristen-b1-results inside #fristen-b1-panel, below the
cascade button row.
- frontend/fristenrechner.ts: extract renderSearchResultsInto() and
wirePillClicks(); runB1Search now writes to fristen-b1-results,
fetches /api/.../search?browse=all when no slug is picked yet (full
landscape on entry), and applies CSS-driven loading dim with a seq
guard against out-of-order responses. Hoisted loadAndRenderB1() so
showBMode("tree") can trigger the tree load on Pathway B entry
(radio.checked = true does not fire change events).
- backend: SearchOptions.BrowseAll, allMappedConceptIDs() returning
the union of every concept reachable from any leaf via
paliad.event_category_concepts, lifted limit ceiling for browse
modes (default 200, max 500). Handler exposes ?browse=all.
- CSS: shared loading-state styling for fristen-b1-results.
This commit is contained in:
@@ -22,11 +22,16 @@ import (
|
||||
// from this taxonomy node and its descendants
|
||||
// appear. Empty q is allowed when this is set
|
||||
// (browse mode).
|
||||
// browse - "all" enables v3 B1 entry mode: returns
|
||||
// every concept mapped to any leaf of the
|
||||
// decision tree (no narrowing, no query).
|
||||
// Ignored when q is non-empty.
|
||||
// forum - comma-separated v3 forum-bucket slugs
|
||||
// (upc_cfi, upc_coa, de_lg, de_olg, de_bgh,
|
||||
// de_bpatg, epa_grant, epa_opp, epa_appeal,
|
||||
// dpma). Trigger pills bypass this filter.
|
||||
// limit - max cards (default 12, max 30)
|
||||
// limit - max cards (default 12, max 30; in browse
|
||||
// modes default 200, max 500)
|
||||
//
|
||||
// Returns an empty cards array (not 400) when q is empty — that lets
|
||||
// the frontend boot the search input without a server round-trip.
|
||||
@@ -44,6 +49,7 @@ func handleFristenrechnerSearch(w http.ResponseWriter, r *http.Request) {
|
||||
Source: r.URL.Query().Get("source"),
|
||||
EventCategorySlug: r.URL.Query().Get("event_category_slug"),
|
||||
Forums: parseCSV(r.URL.Query().Get("forum")),
|
||||
BrowseAll: r.URL.Query().Get("browse") == "all",
|
||||
Limit: parseLimit(r.URL.Query().Get("limit")),
|
||||
}
|
||||
resp, err := dbSvc.deadlineSearch.Search(r.Context(), q, opts)
|
||||
|
||||
@@ -83,6 +83,12 @@ type SearchOptions struct {
|
||||
// v3 (t-paliad-133):
|
||||
EventCategorySlug string // drives B1 decision-tree narrowing
|
||||
Forums []string // multi-select forum buckets (UNION within)
|
||||
// v3 (t-paliad-134): explicit "browse everything" mode for B1 entry,
|
||||
// before the user has picked any tree node. Returns every concept
|
||||
// that is mapped to any leaf via paliad.event_category_concepts —
|
||||
// i.e. the full landscape of B1-reachable concepts. q must be empty
|
||||
// when BrowseAll is true; ignored otherwise.
|
||||
BrowseAll bool
|
||||
Limit int
|
||||
MaxLimit int
|
||||
}
|
||||
@@ -201,12 +207,24 @@ type pillRow struct {
|
||||
// decision-tree cascade in Pathway B).
|
||||
func (s *DeadlineSearchService) Search(ctx context.Context, q string, opts SearchOptions) (*SearchResponse, error) {
|
||||
limit := opts.Limit
|
||||
if limit <= 0 {
|
||||
limit = 12
|
||||
}
|
||||
maxLimit := opts.MaxLimit
|
||||
if maxLimit <= 0 {
|
||||
maxLimit = 30
|
||||
// Browse mode (B1: slug-driven or all-mapped) returns the entire
|
||||
// reachable concept set, which exceeds the trigram-search defaults.
|
||||
// Lift the ceiling so the user sees the full landscape on entry.
|
||||
if opts.BrowseAll || opts.EventCategorySlug != "" {
|
||||
if limit <= 0 {
|
||||
limit = 200
|
||||
}
|
||||
if maxLimit <= 0 {
|
||||
maxLimit = 500
|
||||
}
|
||||
} else {
|
||||
if limit <= 0 {
|
||||
limit = 12
|
||||
}
|
||||
if maxLimit <= 0 {
|
||||
maxLimit = 30
|
||||
}
|
||||
}
|
||||
if limit > maxLimit {
|
||||
limit = maxLimit
|
||||
@@ -219,9 +237,12 @@ func (s *DeadlineSearchService) Search(ctx context.Context, q string, opts Searc
|
||||
}
|
||||
|
||||
qNorm := normalizeQuery(q)
|
||||
browseMode := qNorm == "" && opts.EventCategorySlug != ""
|
||||
browseMode := qNorm == "" && (opts.EventCategorySlug != "" || opts.BrowseAll)
|
||||
|
||||
// v3: resolve the event-category slug to a concept_id allow-list.
|
||||
// When BrowseAll is set without a slug, the allow-list is the union
|
||||
// of every concept reachable from any leaf — i.e. all rows of
|
||||
// paliad.event_category_concepts.
|
||||
var allowConceptIDs []string
|
||||
if opts.EventCategorySlug != "" && s.eventCategory != nil {
|
||||
ids, err := s.eventCategory.ConceptIDsForSlug(ctx, opts.EventCategorySlug)
|
||||
@@ -234,6 +255,15 @@ func (s *DeadlineSearchService) Search(ctx context.Context, q string, opts Searc
|
||||
return resp, nil
|
||||
}
|
||||
allowConceptIDs = ids
|
||||
} else if opts.BrowseAll {
|
||||
ids, err := s.allMappedConceptIDs(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
allowConceptIDs = ids
|
||||
}
|
||||
|
||||
// v3: translate forum slugs to proceeding_code allow-list.
|
||||
@@ -279,6 +309,19 @@ func (s *DeadlineSearchService) Search(ctx context.Context, q string, opts Searc
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// allMappedConceptIDs returns every distinct concept_id that has at
|
||||
// least one row in paliad.event_category_concepts — the universe of
|
||||
// concepts reachable from any leaf of the v3 decision tree. Drives B1
|
||||
// browse-all mode (no slug picked yet, show the full landscape).
|
||||
func (s *DeadlineSearchService) allMappedConceptIDs(ctx context.Context) ([]string, error) {
|
||||
const sqlText = `SELECT DISTINCT concept_id::text FROM paliad.event_category_concepts`
|
||||
var ids []string
|
||||
if err := s.db.SelectContext(ctx, &ids, sqlText); err != nil {
|
||||
return nil, fmt.Errorf("all mapped concept ids: %w", err)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// translateForums maps a list of forum slugs to the union of their
|
||||
// proceeding_type_codes via ForumToProceedingCodes. Unknown slugs are
|
||||
// silently dropped.
|
||||
|
||||
Reference in New Issue
Block a user