# Handling Empty Reports

> How OtisEd.Nimble responds when a report has no cards to display. Covers public (anonymous) viewers, private (authenticated) viewers, and admin / designer pages.
>
> Ticket: [NMB-1809](http://app.asana.com/0/search?q=1809\&searched_type=task\&child=1213985704969868)

## What an "empty report" is

A report (container) is considered **empty** when either of these is true:

* Every card in the container has its **Included** flag set to **off** (`included=false`), or
* The container has no cards at all.

A card with no explicit include setting (null / missing flag) is treated as **included** — so legacy content that predates the flag stays visible by default.

## Public (anonymous) viewers

**Scenario:** A public report page where filters are locked to the URL parameters and the viewer is not signed in.

In public view, empty reports are **hidden from the navigation menu** so anonymous users never land on a dead-end page.

**What the user sees:**

| Situation                                            | Before                                                       | After NMB-1809                                                                    |
| ---------------------------------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| A section's cards are all excluded                   | Section appears in the left nav; clicking shows a blank page | Section is absent from the left nav (desktop and mobile) — no gap, no placeholder |
| A group's child sections are all excluded            | Group header appears with nothing meaningful under it        | Group header is absent; nested hiding cascades up                                 |
| A report has no cards at all                         | Listed in the nav                                            | Absent from the nav                                                               |
| A mixed section (some cards included, some excluded) | Shown                                                        | Still shown — only sections where *every* card is excluded are hidden             |

The check runs once at page load. Since public view uses locked URL filters, nothing changes during the session.

## Private (authenticated) viewers

**Scenario:** A signed-in user with interactive filters (organization, date, term, and so on).

In private view, the navigation menu **never changes** in response to include flags. Stakeholders explicitly requested this: the user's mental model of the nav should stay stable as filters change — losing a highlighted section because a filter happened to hide it is more disorienting than helpful.

Instead, when the currently-selected container has no visible cards for the chosen filters, the card grid is replaced with a clear message:

> 📊 **Report Unavailable**
>
> This report is not available for the filters selected.

**Key behaviors:**

* The left nav and section headers keep their full structure before and after a filter change — same items, same order, same selection still active.
* The empty-state message only affects the **content area**. Users can change filters back (or pick a different report) and the grid repopulates immediately.
* The message is announced to screen readers when it appears (via an ARIA live region), so filter-driven transitions are accessible.

**Reports with zero cards assigned at all** land on the same message, with no need to change filters — the container simply has nothing to show until an editor adds cards.

## Admin / designer pages (unchanged)

The `ContainerManager` designer and admin-cards pages are **not affected** by any of the above. They show every section, every group, and every card regardless of include state so editors can manage the content. No empty-state message ever fires in admin / designer contexts.

This exclusion is built into the architecture: admin / designer trees don't mount the page-level visibility provider that drives nav filtering and empty-state detection.

## Standalone / expanded / print windows

Users can open an individual container in three auxiliary routes:

* **Standalone container page** — a direct `container-view?containerId=…` URL (bookmark- or link-share-friendly).
* **Expanded view** — opened in a new window by clicking the Expand button on a card.
* **Print view** — opened in a new window by clicking the Print button on a card.

All three behave like the private viewer: if the container resolves to no visible cards for the chosen filters (including the filter state encoded in the URL), the empty-state message appears instead of a blank grid.

The **designer playground** route (`container-view-playground?containerId=…`) is treated as a designer view — it always shows every card so editors can preview and adjust, and the empty-state message never fires there.

## Cross-deployment behavior

The handling is identical across every tenant deployment (KY SRC, USVI, and any other branded deployments). The code lives in the base React app and is not overridden by any branding bundle, so behavior matches everywhere by construction.

**Known environment note:** A handful of deployments do not yet ship a native-React public container route (parallel to the MSTR `MSEmbeddedPublic` route). In those deployments, anonymous users never reach the React container viewer, so the public-view nav hiding described above has no surface to appear on. Code correctness is still covered by unit and integration tests; end-to-end verification requires a deployment that has a public container route.

## Accessibility

This feature is built to Section 508 / WCAG 2.1 Level AA:

* **ARIA live region** — the empty-state message wraps its content in `role="status"` + `aria-live="polite"`. When a filter change causes the grid to swap to the message, screen readers announce the new copy to the user.
* **Decorative icon** — the 📊 glyph above the heading is marked `aria-hidden="true"` so the screen-reader announcement contains only the meaningful text, not the icon name.
* **Heading hierarchy** — the "Report Unavailable" text looks like a heading but is implemented as a styled `<div>` (not an `<h*>` tag). This preserves the page's heading outline and prevents out-of-order level jumps that assistive technologies rely on.
* **Keyboard accessibility** — public nav hiding removes hidden items from the DOM (not just visually), so keyboard tab order matches what's visible on screen. No focus can land on a nav item that was filtered out.

## Answers to common questions

**"I'm in private view, I changed a filter, and a section I was working on now shows the 'Report Unavailable' message. Did I do something wrong?"**

No — the selected filters just don't produce any cards for that report. The navigation is intentionally left alone so you don't lose your place. Change one or more filters and the card grid repopulates. If you expected cards to appear, check with the report owner to confirm the cards are marked as "Included" for your selection.

**"I bookmarked a public container page. Now it's missing from the left nav. What happened?"**

In public view, a report becomes hidden from the nav when every card in it is set to "not included" (or when all of the sections under a group resolve to that state). The report owner may have toggled cards off, or the content may have been removed. The direct URL might still work for authenticated users who go through the private nav.

**"As an editor, I set some cards to 'Included = false' in the designer, but they still show in the designer preview. Is that a bug?"**

No — that's the intent. Designer views show every card regardless of the include flag so you can edit cards that are hidden from end users. The include flag only affects what end users see in the public and private viewers.

## Related

* [Report Page Layouts](https://github.com/otised-inc/OtisEd.Nimble/blob/master/docs/report-page-layouts.md) — overview of the report container system
* Technical: the visibility logic lives in `src/OtisEd.Nimble.Web/ReactApp/src/reporting-containers/utilities/SectionVisibility.ts` and the context plumbing in `src/OtisEd.Nimble.Web/ReactApp/src/reporting-containers/contexts/SectionVisibilityContext.tsx`. The empty-state component is at `src/OtisEd.Nimble.Web/ReactApp/src/container-cards-designer/components/ContainerEmptyStateMessage.tsx`.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://nimble.docs.otised.com/guides/empty-report-handling.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
