@remit/ui 0.0.43 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -227,6 +227,26 @@ export const DegradedStandingWiden: Story = {
|
|
|
227
227
|
},
|
|
228
228
|
};
|
|
229
229
|
|
|
230
|
+
/**
|
|
231
|
+
* Editing a persisted filter (RFC 038 D6): scope and expiry render read-only,
|
|
232
|
+
* with a note that they — and the semantic anchor — are fixed at creation. The
|
|
233
|
+
* name and the literal clauses stay editable; the widen chip is display-only.
|
|
234
|
+
*/
|
|
235
|
+
export const LifecycleLocked: Story = {
|
|
236
|
+
args: {
|
|
237
|
+
rule: {
|
|
238
|
+
...demoRule,
|
|
239
|
+
scope: "until",
|
|
240
|
+
until: "2027-09-01",
|
|
241
|
+
name: "Conference",
|
|
242
|
+
},
|
|
243
|
+
folders: demoFolders,
|
|
244
|
+
preview: READY(31),
|
|
245
|
+
semanticAvailable: false,
|
|
246
|
+
lifecycleLocked: true,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
230
250
|
/** Nothing to match yet — the commit says why it is blocked. */
|
|
231
251
|
export const BlockedEmpty: Story = {
|
|
232
252
|
args: {
|
|
@@ -49,6 +49,15 @@ export interface FilterRuleEditorProps {
|
|
|
49
49
|
* match, so the editor never offers a clause the backend cannot evaluate.
|
|
50
50
|
*/
|
|
51
51
|
clauseFields?: ClauseField[];
|
|
52
|
+
/**
|
|
53
|
+
* Render the scope and expiry read-only (RFC 038 D6). A persisted filter's
|
|
54
|
+
* scope, expiry, and semantic anchor are fixed at creation — the update
|
|
55
|
+
* endpoint carries none of them — so editing a filter shows them as a static
|
|
56
|
+
* summary with a note rather than live controls that would silently discard
|
|
57
|
+
* the change (reader #266 tracks lifting this). The name and the literal
|
|
58
|
+
* predicate stay editable.
|
|
59
|
+
*/
|
|
60
|
+
lifecycleLocked?: boolean;
|
|
52
61
|
/** The inline clause form, when adding or editing a clause. */
|
|
53
62
|
clauseEdit?: ClauseEditState;
|
|
54
63
|
onStartAddClause?: () => void;
|
|
@@ -86,6 +95,7 @@ export function FilterRuleEditor({
|
|
|
86
95
|
preview,
|
|
87
96
|
semanticAvailable = false,
|
|
88
97
|
clauseFields,
|
|
98
|
+
lifecycleLocked = false,
|
|
89
99
|
clauseEdit,
|
|
90
100
|
onStartAddClause,
|
|
91
101
|
onStartEditClause,
|
|
@@ -211,14 +221,22 @@ export function FilterRuleEditor({
|
|
|
211
221
|
|
|
212
222
|
<section className="space-y-2">
|
|
213
223
|
<p className="text-xs font-medium text-fg-muted">How long</p>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
224
|
+
{lifecycleLocked ? (
|
|
225
|
+
<p className="text-xs text-fg">
|
|
226
|
+
{rule.scope === "until"
|
|
227
|
+
? `Until ${rule.until ?? ""}`
|
|
228
|
+
: "Always — runs on matching mail"}
|
|
229
|
+
</p>
|
|
230
|
+
) : (
|
|
231
|
+
<SegmentedControl
|
|
232
|
+
name="rule-scope"
|
|
233
|
+
size="sm"
|
|
234
|
+
aria-label="Rule scope"
|
|
235
|
+
options={scopeOptions}
|
|
236
|
+
value={rule.scope}
|
|
237
|
+
onChange={(value) => onChangeScope?.(value)}
|
|
238
|
+
/>
|
|
239
|
+
)}
|
|
222
240
|
{needsName && (
|
|
223
241
|
<Input
|
|
224
242
|
value={rule.name ?? ""}
|
|
@@ -228,7 +246,7 @@ export function FilterRuleEditor({
|
|
|
228
246
|
className="w-full"
|
|
229
247
|
/>
|
|
230
248
|
)}
|
|
231
|
-
{rule.scope === "until" && (
|
|
249
|
+
{!lifecycleLocked && rule.scope === "until" && (
|
|
232
250
|
<div className="flex items-center gap-2 text-xs text-fg-muted">
|
|
233
251
|
<span>Until</span>
|
|
234
252
|
<Input
|
|
@@ -240,6 +258,13 @@ export function FilterRuleEditor({
|
|
|
240
258
|
/>
|
|
241
259
|
</div>
|
|
242
260
|
)}
|
|
261
|
+
{lifecycleLocked && (
|
|
262
|
+
<p className="text-2xs text-fg-subtle">
|
|
263
|
+
{rule.widen
|
|
264
|
+
? "The scope, expiry, and similar-mail match are set when a filter is created."
|
|
265
|
+
: "The scope and expiry are set when a filter is created."}
|
|
266
|
+
</p>
|
|
267
|
+
)}
|
|
243
268
|
</section>
|
|
244
269
|
|
|
245
270
|
<div className="border-t border-line pt-3">
|
|
@@ -554,6 +554,37 @@ describe("FilterRuleEditor", () => {
|
|
|
554
554
|
it("shows the live preview region", () => {
|
|
555
555
|
assert.match(editor(), /47 messages match/);
|
|
556
556
|
});
|
|
557
|
+
|
|
558
|
+
it("renders scope and expiry read-only when the lifecycle is locked", () => {
|
|
559
|
+
const html = editor({
|
|
560
|
+
rule: { ...demoRule, scope: "until", until: "2027-09-01" },
|
|
561
|
+
lifecycleLocked: true,
|
|
562
|
+
});
|
|
563
|
+
// No live scope toggle, no editable date input — a static summary and a note.
|
|
564
|
+
assert.doesNotMatch(html, /aria-label="Rule scope"/);
|
|
565
|
+
assert.doesNotMatch(html, /aria-label="Expiry date"/);
|
|
566
|
+
assert.match(html, /Until 2027-09-01/);
|
|
567
|
+
assert.match(html, /set when a filter is created/);
|
|
568
|
+
// The name stays editable.
|
|
569
|
+
assert.match(html, /aria-label="Rule name"/);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
it("names the similar-mail match in the locked note only when a widen is present", () => {
|
|
573
|
+
const withWiden = editor({
|
|
574
|
+
rule: { ...demoRule, widen: { anchorCount: 2 } },
|
|
575
|
+
lifecycleLocked: true,
|
|
576
|
+
});
|
|
577
|
+
assert.match(
|
|
578
|
+
withWiden,
|
|
579
|
+
/similar-mail match are set when a filter is created/,
|
|
580
|
+
);
|
|
581
|
+
const literal = editor({
|
|
582
|
+
rule: { ...demoRule, widen: undefined },
|
|
583
|
+
lifecycleLocked: true,
|
|
584
|
+
});
|
|
585
|
+
assert.match(literal, /scope and expiry are set when a filter is created/);
|
|
586
|
+
assert.doesNotMatch(literal, /similar-mail match/);
|
|
587
|
+
});
|
|
557
588
|
});
|
|
558
589
|
|
|
559
590
|
describe("FilterRuleDialog", () => {
|