feat(where): the shared control for an alarm's delivery scope - #733
Merged
Conversation
First half of the per-alarm scope UI. The chip and the sheet exist and are covered; wiring them into the nine alarm dialogs, the card templates and a Places screen is the follow-up. Every alarm has always answered "where should this reach me". Before PoracleNG 5.1.0 the answer was inherited and invisible: one profile pin, one area list, shared by everything. This makes the answer visible and editable per alarm, so the work is stating something that was already there rather than bolting on an override system -- which is also why nothing in the UI says "override". - alarm-scope.ts reads the two override columns and the radius back into one discriminated value, and writes it out again. That is what lets the sheet offer a radio group instead of three fields and three error messages: the states PoracleNG refuses cannot be expressed. - Clearing sends empty values, never null. Null means "not stated, keep what is stored" on the write path, so a null here would make an override impossible to take off. - WhereChipComponent renders the scope as a sentence fragment and is the way into the sheet, so the same control reads and edits the same idea. - WhereSheetComponent offers the three modes. Its area picker lists the user's own geofences alongside admin areas, which is only honest because the backend now writes those past PoracleNG's user-selectable filter. - PlacesService holds the saved places as a signal, since the sheet, the Places screen and the cards all read the same list. Locale keys added to all eleven files, English text throughout pending translation.
Second slice of the per-alarm scope UI. "Near a place" is unusable without somewhere to make places, so this is the half that has to land with it. - PlacesDialogComponent, in the user menu beside Alert Defaults: the profile pin, the named places, add and delete. - LocationDialogComponent gains `pickOnly`. It saved the chosen point as the profile pin unconditionally, which is what it was for -- so borrowing it to name a place would have quietly relocated every alarm without an override. Three existing callers are untouched. - Naming reuses ConfirmDialog's promptField, which already does name-with-duplicate-check, rather than adding a third dialog that asks for one string. The existing labels are passed in so it can refuse a duplicate before the request goes out. - Delete surfaces the 409 referencing_rules count. PoracleNG refuses to orphan a label, and the useful thing to say is how many alerts are in the way, not that it failed. Seven component tests, including both delete failure paths and the cancelled naming step. The pick-only assertion is there because the failure it guards against is silent: without it the pin moves and nothing says so.
Wiring the first card found a bug in the model I shipped yesterday. scopeOf collapsed "within N km of my pin" into the inherited reading, so an alarm with a radius and no place rendered as "Anywhere in my areas" -- the opposite of what it does, in the one place the card is supposed to be authoritative. A radius with no place is the behaviour that predates per-alarm scope and it needs its own reading, so the profile mode now carries a distance and describeScope words it separately. That also corrected the sheet. Offering "areas / near a place / specific areas" left no way to express the most common alarm there is. The second option is now "near a point" with a target select whose first entry is the pin, because to a person that is one choice with a target rather than two unrelated modes. Three options still, and they still map onto the three mutual-exclusion rules PoracleNG enforces. - WhereChipComponent replaces the areas-or-distance badge on the Pokemon card. Clicking it opens the sheet, so scope can be changed without opening the whole edit dialog. - The list loads the profile's selected areas, only so the inherited wording can be honest: "anywhere in my areas" is a lie for someone with none. - 11 new tests across the chip and the scope model, including both readings of a bare radius, which is the case that was wrong.
The add dialog's radius gains a "measured from" selector: the pin, as before, or a saved place. Everything funnels through scopeToFields, so the card chip, the scope sheet and this dialog produce the same wire format from one implementation. The edit dialog deliberately does NOT gain the control. Scope is changed from the card chip, which is one control in one place; a second editor would be two paths that can disagree. What the dialog needed was to stop being able to destroy a scope it cannot describe: - An area-confined alarm has no radius, so the areas-or-distance control cannot express it. The control is hidden for those and the stored distance is sent back unchanged, rather than offering a change that would silently discard the areas. - The dialog shows the scope read-only, so an alarm aimed at a place does not look like a plain radius while being edited. Three add-dialog tests, including the one that matters most: a plain radius must NOT acquire a location override just because the field now exists. Full frontend suite: 1025 passing.
hokiepokedad2
marked this pull request as ready for review
August 19, 2026 04:14
# Conflicts: # CHANGELOG.md
The production build resolves template pipes; jest and tsc do not. `| number` on the coordinate rows compiled locally and failed CI with NG8004. Verification for Angular template changes needs `npm run build`, not just the type check and the unit tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Draft. First half of the per-alarm scope UI, from the proposal. Depends on #732 for the fields it reads and writes.
What is here
alarm-scope.ts— readsoverride_location_label,override_areasand the radius back into one discriminated value, and writes it out again. This is the load-bearing piece: PoracleNG stores three independent columns and enforces their mutual exclusion with three validation rules, and collapsing them into one choice is what lets the sheet be a radio group instead of three fields plus error copy. The states PoracleNG refuses cannot be expressed.WhereChipComponent— the scope as a sentence fragment ("Within 2 km of Home", "Only in Terrigal, Erina"), and the way into the sheet. The inherited case is deliberately quiet since it is on nearly every card.WhereSheetComponent— the three modes. Its area picker lists the user's own geofences next to admin areas, which is only honest because feat: per-alarm delivery scope, including user-drawn geofences #732 writes those past PoracleNG's user-selectable filter.PlacesService— saved places as a signal, because the sheet, the Places screen and the cards read the same list and a place added in one has to appear in the others.Two decisions worth reviewing
Nothing says "override". People think "where", not
override_location_label. The framing throughout is that every alarm already had an answer to this question and we are making it visible, not adding a new capability.Clearing sends empty values, never null. Null means "not stated, keep what is stored" on the write path (#731), so sending null would make an override impossible to take off.
scopeToFieldsis where that lives, and it has a test saying so.Tests
14 cases on the scope utilities, including two that encode real judgement calls rather than mechanics:
Component specs for the chip and sheet come with the wiring, when there is a card to render them in.
Still to do
Wiring into the nine alarm dialogs and their card templates, a Places screen under the user menu (with the 409
referencingRulesflow, which is the interesting part of deleting a place), theAlertDefaultsServiceextension for a default place, and the mega-PVP segmented control. Locale keys are in all eleven files with English text pending translation.Screenshots
None yet — the sheet has no entry point until the dialogs are wired. Worth a look then rather than now.