Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5a6db289d | ||
|
|
d8ccb5b733 | ||
|
|
c3f044bd65 | ||
|
|
59265f78b6 |
No files matched your search
+22
-3
@@ -206,11 +206,30 @@ registry is orphaned and nothing reads it.
|
|||||||
variable, and set `UNLEASH_URL` to `http://<UNLEASH_IP>:4242/api`.
|
variable, and set `UNLEASH_URL` to `http://<UNLEASH_IP>:4242/api`.
|
||||||
5. Redeploy the application stacks.
|
5. Redeploy the application stacks.
|
||||||
|
|
||||||
|
### Adding a flag to Unleash
|
||||||
|
|
||||||
|
**Unleash does not create flags by itself.** The SDK reads definitions from the
|
||||||
|
server and never registers anything, and metrics for a flag the server has
|
||||||
|
never heard of are discarded. So a flag declared in `backend/flags.py` will be
|
||||||
|
evaluated on every request, stay `False` forever, and never appear in the UI
|
||||||
|
until someone creates it there by hand.
|
||||||
|
|
||||||
|
For each flag in the registry, create one in Unleash with:
|
||||||
|
|
||||||
|
- **Name** — character for character what `backend/flags.py` declares.
|
||||||
|
snake_case, no hyphens or spaces. A typo produces a flag that looks correct
|
||||||
|
in the UI and is read by nothing.
|
||||||
|
- **Type** — Release. No strategies, constraints or variants: these are plain
|
||||||
|
on/off switches, by design.
|
||||||
|
|
||||||
### Turning a feature on
|
### Turning a feature on
|
||||||
|
|
||||||
Toggle the flag in the environment you want. Flags appear in the Unleash UI
|
Toggle the flag in the environment matching the stack you mean: **development**
|
||||||
after the backend has evaluated them once, so a newly declared flag shows up
|
for staging, **production** for prod. The token in each stack is scoped to one
|
||||||
shortly after the deploy that introduced it.
|
environment, so toggling the other one has no visible effect.
|
||||||
|
|
||||||
|
The SDK refreshes every 15 seconds, so the API reflects the change almost at
|
||||||
|
once; the pages follow on their own schedule, below.
|
||||||
|
|
||||||
A flip reaches school pages within about five minutes and place pages within
|
A flip reaches school pages within about five minutes and place pages within
|
||||||
the hour. Next's ISR does the propagating — it revalidates a route at the
|
the hour. Next's ISR does the propagating — it revalidates a route at the
|
||||||
|
|||||||
@@ -2163,6 +2163,41 @@ test('typing a school name suggests it, and choosing it opens that school', asyn
|
|||||||
await expect(page).toHaveURL(/\/school\/\d+/);
|
await expect(page).toHaveURL(/\/school\/\d+/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('the whole dropdown is reachable, not clipped by the hero', async ({ page }) => {
|
||||||
|
/*
|
||||||
|
* The hero panel had overflow: hidden to clip its artwork to the rounded
|
||||||
|
* corners, and it clipped the dropdown too — 320px of list against 145px of
|
||||||
|
* panel below the input, so roughly half was cut off with nothing to say so.
|
||||||
|
*
|
||||||
|
* toBeVisible() does not catch this: it checks the box is non-empty and not
|
||||||
|
* visibility:hidden, and an ancestor's overflow clips neither. The invariant
|
||||||
|
* that does catch it is that the LAST option is the thing actually painted
|
||||||
|
* at its own coordinates — which fails for clipping and for occlusion alike.
|
||||||
|
*/
|
||||||
|
test.skip(!(await autosuggestIsOn(page)),
|
||||||
|
'the school_autosuggest flag is off in this environment');
|
||||||
|
|
||||||
|
const { schools } = await (await page.request.get('/api/schools?page_size=1')).json();
|
||||||
|
test.skip(!schools?.length, 'no schools in this environment');
|
||||||
|
|
||||||
|
await page.goto('/');
|
||||||
|
await page.getByRole('combobox').first().fill(
|
||||||
|
(schools[0].school_name as string).slice(0, 6));
|
||||||
|
|
||||||
|
const options = page.getByRole('option');
|
||||||
|
await expect(options.first()).toBeVisible();
|
||||||
|
const count = await options.count();
|
||||||
|
|
||||||
|
const painted = await options.nth(count - 1).evaluate((el) => {
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2);
|
||||||
|
return { inside: el.contains(hit) || el === hit, bottom: Math.round(r.bottom) };
|
||||||
|
});
|
||||||
|
expect(painted.inside,
|
||||||
|
`the last option is not painted at its own coordinates (bottom ${painted.bottom}) `
|
||||||
|
+ '— an ancestor is clipping or covering the dropdown').toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
test('with autosuggest off, the search box is a plain input', async ({ page }) => {
|
test('with autosuggest off, the search box is a plain input', async ({ page }) => {
|
||||||
test.skip(await autosuggestIsOn(page),
|
test.skip(await autosuggestIsOn(page),
|
||||||
'the school_autosuggest flag is on in this environment');
|
'the school_autosuggest flag is on in this environment');
|
||||||
|
|||||||
@@ -90,7 +90,18 @@
|
|||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
background: var(--hero-ground);
|
background: var(--hero-ground);
|
||||||
border-radius: var(--radius-xl);
|
border-radius: var(--radius-xl);
|
||||||
overflow: hidden;
|
/*
|
||||||
|
* Deliberately NOT overflow: hidden.
|
||||||
|
*
|
||||||
|
* It used to be, to clip the artwork and the scrim to the rounded corners —
|
||||||
|
* and it also clipped the search box's suggestion dropdown, which is 320px
|
||||||
|
* tall against 145px of panel below the input. Roughly half the list was cut
|
||||||
|
* off with no indication anything was missing.
|
||||||
|
*
|
||||||
|
* The two things that actually needed clipping round themselves instead, so
|
||||||
|
* the panel can let a dropdown out. Anything absolutely positioned inside
|
||||||
|
* this panel and taller than the space below it depends on this.
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.heroContent {
|
.heroContent {
|
||||||
@@ -107,6 +118,10 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
/* Rounds itself, because the panel no longer clips it. inset: 0 makes this
|
||||||
|
exactly the panel's own corners. */
|
||||||
|
border-radius: inherit;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.heroArt picture,
|
.heroArt picture,
|
||||||
@@ -143,6 +158,9 @@
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
/* Same reason as .heroArt: the panel stopped clipping, so the scrim keeps
|
||||||
|
its own corners rather than squaring off over the panel's. */
|
||||||
|
border-radius: inherit;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
to right,
|
to right,
|
||||||
var(--hero-ground) 0%,
|
var(--hero-ground) 0%,
|
||||||
@@ -331,6 +349,10 @@
|
|||||||
position: static;
|
position: static;
|
||||||
order: -1;
|
order: -1;
|
||||||
height: 13rem;
|
height: 13rem;
|
||||||
|
/* Top corners only. Here the artwork is a band flush with the top of the
|
||||||
|
panel, not a layer covering it — inheriting all four would leave it
|
||||||
|
floating with rounded bottom corners against the copy below. */
|
||||||
|
border-radius: var(--radius-xl) var(--radius-xl) 0 0;
|
||||||
}
|
}
|
||||||
/* The band crop puts the schoolhouse at 73% across — reported by
|
/* The band crop puts the schoolhouse at 73% across — reported by
|
||||||
scripts/build-hero-images.js, which derives it from the crop box rather
|
scripts/build-hero-images.js, which derives it from the crop box rather
|
||||||
|
|||||||
Reference in new issue
Block a user