Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ UI layering is documented in `FRONTEND.md` — read it before adding styles. Sho
| `staging` | push | App Engine staging (`CD_staging.yml`) |
| `production` | push | App Engine production (`CD_production.yml`) |

### Where unfinished work gets exercised

**Preview deploys are the sandbox. `staging` is a pre-production release branch — what is on it is a candidate for `production`, not an experiment.** Anything not ready to ship gets exercised on its own PR preview (see `docs/preview-deployments.md`), which can run against an ephemeral API nobody else shares.

In code, that means WIP surfaces gate on dev or preview, never on staging:

```ts
export const SHOW_WIP_FEATURES =
import.meta.env.DEV || import.meta.env.VITE_APP_ENV === 'preview'
```

`recordsGridLogic.ts` gates exactly this way. Do not add `'staging'` to that check, and do not add a staging arm to a new one.

### Where to branch from

**Everything bases off `staging` and targets `staging` in its PR — feature, fix, chore, docs, and CI work alike.** The single exception is a hotfix, which bases off `production` and targets `production`.
Expand Down
4 changes: 1 addition & 3 deletions docs/access-control-ruleset.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ This mapping is temporary and should be removed before the v1 release once all u
- Can view confidential data

### AMP.Admin Only
- Can access `Sandbox`
- Can access unfinished / WIP AMP resources

### Geothermal Roles
Expand Down Expand Up @@ -73,8 +72,7 @@ This mapping is temporary and should be removed before the v1 release once all u
- `ocotillo.thing-well-batch-export` -> AMP view access required
- `ocotillo.groundwater-level-observation` -> AMP view access required

### AMP / Sandbox
- `Sandbox` -> `AMP.Admin` only
### AMP / Water
- `water.*` -> AMP access required
- WIP `water.*` resources -> `AMP.Admin` only
- `water.locations` -> `AMP.Admin` or `Geothermal.Admin`
Expand Down
9 changes: 4 additions & 5 deletions docs/geothermal-well-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,8 @@ as the records grid (bypassed in local dev, enforced in prod).
## 8. Navigation & route

- Route: `/geothermal/wells/inventory`.
- Nav: a **Geothermal Inventory** entry (Sandbox section for now, consistent
with the current geothermal placement; promote to a real geothermal nav
group when the feature graduates).
- Nav: none yet. The page is reachable by URL while the work is in progress;
it gets a nav entry when a geothermal nav group lands.

---

Expand All @@ -164,7 +163,7 @@ as the records grid (bypassed in local dev, enforced in prod).
- Boolean, date, and **dropdown** cell kinds in `EditableDataGrid`, plus
allowed-value lists for the enum fields.
- Inventory page (grid + toolbar: Add rows, Upload CSV, Download template, Save).
- Route + nav entry (Sandbox).
- Route (no nav entry yet).
- Create-only save wrapper (adapt records-grid save to POST-only).

---
Expand All @@ -176,7 +175,7 @@ as the records grid (bypassed in local dev, enforced in prod).
surface the conflict inline. No client-side dedupe/upsert in v1. ✔
- **Enum fields** (`well_type`, `well_class`, `status`): **dropdowns** from
fixed allowed-value lists (new select cell kind). ✔
- **Nav placement:** Sandbox (temporary). ✔
- **Nav placement:** none yet — URL-only until a geothermal nav group lands. ✔
- **Records vs inventory:** inventory is create-wells only; the existing records
grid is untouched. ✔

Expand Down
2 changes: 1 addition & 1 deletion docs/geothermal-well-search-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ endpoint at all.

## Background

The geothermal Sandbox pages (Records Grid, Temp-Depth log) start with a well
The geothermal grid pages (Records Grid, Temp-Depth log) start with a well
picker. It used to be a single dropdown populated by one request:

```
Expand Down
95 changes: 3 additions & 92 deletions src/components/AppShell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useContext, useEffect, useRef, useState } from 'react'
import { cn } from '@/lib/utils'
import { useIsMobile } from '@/hooks/use-mobile'
import { Outlet, Link, useLocation, useNavigate } from 'react-router'
import { Outlet, Link, useLocation } from 'react-router'
import {
CanAccess,
useCustomMutation,
Expand Down Expand Up @@ -50,7 +50,6 @@ import {
Check,
ChevronDown,
ChevronRight,
FlaskConical,
Lock,
LogOut,
Menu,
Expand All @@ -63,7 +62,7 @@ import {
import { ColorModeContext } from '@/contexts'
import SearchBar from '@/components/SearchBar'
import { ReportBugButton } from '@/components/Button'
import { AmpRole, PRIMARY_NAV, RESOURCE_NAV, SHOW_EXAMPLE_NAV, type NavItem } from '@/config/navigation'
import { AmpRole, PRIMARY_NAV, RESOURCE_NAV, type NavItem } from '@/config/navigation'
import { useAccessCapabilities } from '@/hooks'
import { useSearch } from '@/providers/search-provider'
import { SupportPanelContext } from '@/components/SupportPanelContext'
Expand Down Expand Up @@ -425,7 +424,7 @@ function AppSidebar() {

<SidebarSeparator className="my-1 bg-border" />

{/* Resource navigation + temporary Example section — all in one group */}
{/* Resource navigation */}
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
Expand All @@ -437,8 +436,6 @@ function AppSidebar() {
canSeeNavItem={canSeeNavItem}
/>
))}
{/* Example demos — toggle SHOW_EXAMPLE_NAV in config/navigation.ts */}
{SHOW_EXAMPLE_NAV ? <ExampleNavItem /> : null}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
Expand Down Expand Up @@ -469,92 +466,6 @@ function AppSidebar() {
)
}

const SANDBOX_GEOTHERMAL_GRID = '/geothermal/wells/records-grid'
const SANDBOX_GEOTHERMAL_INVENTORY = '/geothermal/wells/inventory'
const SANDBOX_GEOTHERMAL_TEMP_DEPTH = '/geothermal/wells/temp-depth'

function isSandboxPath(pathname: string): boolean {
return (
pathname.startsWith('/example') ||
pathname.startsWith(SANDBOX_GEOTHERMAL_GRID) ||
pathname.startsWith(SANDBOX_GEOTHERMAL_INVENTORY) ||
pathname.startsWith(SANDBOX_GEOTHERMAL_TEMP_DEPTH)
)
}

function ExampleNavItem() {
const location = useLocation()
const navigate = useNavigate()
const [open, setOpen] = useState(isSandboxPath(location.pathname))

useEffect(() => {
if (!isSandboxPath(location.pathname)) setOpen(false)
}, [location.pathname])

const handleClick = () => {
setOpen(true)
navigate('/example/typography')
}

return (
<Collapsible open={open} onOpenChange={setOpen} className="group/example">
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<SidebarMenuButton tooltip="Sandbox" onClick={handleClick}>
<FlaskConical />
<span>Sandbox</span>
<ChevronRight className="ml-auto size-3.5 transition-transform duration-100 group-data-[state=open]/example:rotate-90" />
</SidebarMenuButton>
</CollapsibleTrigger>
<CollapsibleContent>
<SidebarMenuSub>
<SidebarMenuSubItem>
<SidebarMenuSubButton
asChild
isActive={location.pathname === '/example/typography'}
>
<Link to="/example/typography">Typography</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton
asChild
isActive={location.pathname.startsWith(SANDBOX_GEOTHERMAL_GRID)}
>
<Link to={SANDBOX_GEOTHERMAL_GRID}>Geothermal Records</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton
asChild
isActive={location.pathname.startsWith(
SANDBOX_GEOTHERMAL_INVENTORY
)}
>
<Link to={SANDBOX_GEOTHERMAL_INVENTORY}>
Geothermal Inventory
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
<SidebarMenuSubItem>
<SidebarMenuSubButton
asChild
isActive={location.pathname.startsWith(
SANDBOX_GEOTHERMAL_TEMP_DEPTH
)}
>
<Link to={SANDBOX_GEOTHERMAL_TEMP_DEPTH}>
Geothermal Temp-Depth
</Link>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
</SidebarMenuSub>
</CollapsibleContent>
</SidebarMenuItem>
</Collapsible>
)
}

function SupportPanelTrigger({ collapsed }: { collapsed: boolean }) {
const { isOpen, open, close } = useContext(SupportPanelContext)
return (
Expand Down
4 changes: 1 addition & 3 deletions src/components/layout/sider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ export const ThemedSiderV2: React.FC<RefineThemedLayoutSiderProps> = ({
const isAdminOnly = isResourceListAdminOnly(resourceName)

const icon = deprecatedIcon ?? meta?.icon
const derivedLabel = meta?.label || deprecatedLabel || name
const label =
name === 'Sandbox' || name === 'sandbox' ? 'Sandbox' : derivedLabel
const label = meta?.label || deprecatedLabel || name
const isSelected = key === selectedKey
const isNested = meta?.parent !== undefined
const nestedLevel = isNested ? meta?.nestedLevel || 1 : 0
Expand Down
10 changes: 0 additions & 10 deletions src/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,6 @@ export type NavItem = {
children?: NavItem[]
}

/**
* Show the Sandbox nav (typography specimen, WIP geothermal grids).
* Visible in local dev and on PR preview / staging deploys
* (VITE_APP_ENV=preview|staging), so reviewers can exercise the WIP work;
* hidden on production.
*/
export const SHOW_EXAMPLE_NAV =
import.meta.env.DEV ||
['preview', 'staging'].includes(import.meta.env.VITE_APP_ENV)

/**
* Top bar: views and tools.
* Items without `roles` are visible to every authenticated user.
Expand Down
2 changes: 1 addition & 1 deletion src/pages/geothermal/wells/WellPickerPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface WellPickerPageProps {
}

/**
* Shared Sandbox entry point for per-well geothermal pages (records grid,
* Shared entry point for per-well geothermal pages (records grid,
* temp-depth log): find a well, then navigate into `targetPath/{well_data_id}`.
* Admin-gated per BDMS-878.
*
Expand Down
2 changes: 1 addition & 1 deletion src/pages/geothermal/wells/records-grid-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WellPickerPage } from './WellPickerPage'

/** Sandbox entry point for the geothermal records grid. */
/** Entry point for the geothermal records grid. */
export const GeoThermalRecordsGridPicker = () => (
<WellPickerPage
title="Geothermal records"
Expand Down
2 changes: 1 addition & 1 deletion src/pages/geothermal/wells/temp-depth-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { WellPickerPage } from './WellPickerPage'

/** Sandbox entry point for the temperature-depth log. */
/** Entry point for the temperature-depth log. */
export const GeoThermalTempDepthPicker = () => (
<WellPickerPage
title="Temp-depth log"
Expand Down
2 changes: 0 additions & 2 deletions src/resources/ocotillo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ const ocotillo = [
list: '/ocotillo/hydrograph-correction',
meta: {
label: 'Hydrograph Correction',
parent: 'Sandbox',
nestedLevel: 1,
icon: <Timeline />,
},
},
Expand Down
15 changes: 0 additions & 15 deletions src/test/utils/accessControl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,6 @@ const specialResourceExpectations: Array<{
action: 'show',
expected: true,
},
{
name: 'AMP viewer cannot access sandbox special resource',
groups: ['AMP.Viewer'],
resource: 'Sandbox',
action: 'list',
expected: false,
},
{
name: 'AMP admin can access sandbox special resource',
groups: ['AMP.Admin'],
resource: 'Sandbox',
action: 'show',
expected: true,
},
{
name: 'AMP viewer cannot access ocotillo hydrograph correction',
groups: ['AMP.Viewer'],
Expand Down Expand Up @@ -440,7 +426,6 @@ describe('isResourceListAdminOnly', () => {
it('returns true for list resources restricted to admin roles', () => {
expect(isResourceListAdminOnly('ocotillo.location')).toBe(true)
expect(isResourceListAdminOnly('ocotillo.lexicon')).toBe(true)
expect(isResourceListAdminOnly('Sandbox')).toBe(true)
expect(isResourceListAdminOnly('water.locations')).toBe(true)
})

Expand Down
4 changes: 1 addition & 3 deletions src/utils/accessControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ const resourcePolicies: Record<string, ResourcePolicy> = {
delete: adminRoles,
manage: adminRoles,
},
Sandbox: { list: adminRoles, show: adminRoles },
geothermal: { list: geothermalViewerRoles, show: geothermalViewerRoles },
'water.locations': {
list: ['AMP.Admin', 'Geothermal.Admin'],
Expand Down Expand Up @@ -265,8 +264,7 @@ export const canAccessResource = ({

if (
resource === 'ocotillo.hydrograph-correction' ||
resource === 'ocotillo.thing-well-pdf-preview' ||
resource === 'Sandbox'
resource === 'ocotillo.thing-well-pdf-preview'
) {
const policy = resourcePolicies[resource]
return matchesPolicy(policy[action], capabilities.roles)
Expand Down
Loading