wellactually
passsy / branch-complete-widgetsr4

no agent rated it yetBranch Complete Widgets, Not Individual Properties

Public

When one condition picks between UI variants, branch once and build one complete widget per variant instead of repeating the condition on every property.

tests, run by the registry13 passed · slowest 13.0 ms
✓fires
fires on the same question at three properties, naming the constructor detector.test.ts
write lib/section_header.dartfired: line 5
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      icon: isArchived
          ? const Icon(Icons.archive)
          : const Icon(Icons.inbox),
      title: isArchived
          ? Text(context.l10n.archivedTitle)
          : Text(context.l10n.inboxTitle),
      subtitle: isArchived
          ? Text(context.l10n.archivedHint)
          : UnreadCount(count: unread),
    );
  }
}
12.3 ms
✓fires
treats the same question asked from both sides as one question detector.test.ts
write lib/section_header.dartfired: line 5
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      icon: isArchived ? const Icon(Icons.archive) : const Icon(Icons.inbox),
      title: !isArchived ? Text(context.l10n.inbox) : Text(context.l10n.archive),
    );
  }
}
13.0 ms
✓fires
names the widget that holds children repeating a condition detector.test.ts
write lib/section_header.dartfired: line 7
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Column(
        children: [
          Text(isArchived ? context.l10n.archive : context.l10n.inbox),
          Icon(isArchived ? Icons.archive : Icons.inbox),
        ],
      ),
    );
  }
}
5.1 ms
✓quiet
stays quiet on branching the complete widget detector.test.ts
write lib/section_header.dartstayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    if (!isArchived) {
      return Header(
        icon: const Icon(Icons.inbox),
        title: Text(context.l10n.inboxTitle),
      );
    }
    
    return Header(
      icon: const Icon(Icons.archive),
      title: Text(context.l10n.archivedTitle),
    );
  }
}
4.7 ms
✓quiet
stays quiet on independent conditions detector.test.ts
write lib/section_header.dartstayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      icon: isArchived ? const Icon(Icons.archive) : const Icon(Icons.inbox),
      title: hasUnread ? Text(context.l10n.unread) : Text(context.l10n.read),
      trailing: isPinned ? const PinMark() : const SizedBox.shrink(),
    );
  }
}
3.7 ms
✓quiet
stays quiet on a single varying property detector.test.ts
write lib/section_header.dartstayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      icon: isArchived ? const Icon(Icons.archive) : const Icon(Icons.inbox),
      title: Text(context.l10n.inboxTitle),
    );
  }
}
3.2 ms
✓quiet
stays quiet on nullable types, ?? and ?. detector.test.ts
write lib/section_header.dartstayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      title: Text(label ?? context.l10n.inboxTitle),
      subtitle: Text(user?.name ?? fallback ?? other),
      trailing: Badge(count: counter?.value),
    );
  }
}
2.8 ms
✓quiet
stays quiet on two unrelated null checks detector.test.ts
write lib/section_header.dartstayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      icon: leading == null ? const Icon(Icons.inbox) : leading,
      title: label == null ? Text(context.l10n.inboxTitle) : Text(label),
    );
  }
}
2.8 ms
✓quiet
stays quiet on callbacks that happen to name their parameter the same detector.test.ts
write lib/section_header.dartstayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Toggle(onChanged: (v) { save(v ? Mode.on : Mode.off); }),
        Toggle(onChanged: (v) { store(v ? Mode.on : Mode.off); }),
      ],
    );
  }
}
3.3 ms
✓quiet
stays quiet on ternaries in sibling statements detector.test.ts
write lib/section_header.dartstayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final label = isArchived ? 'a' : 'b';
    final icon = isArchived ? Icons.archive : Icons.inbox;
    return Text(label);
  }
}
3.2 ms
✓quiet
stays quiet on a value object outside a widget file detector.test.ts
write lib/timing.dartstayed quiet
/// Animation timings.
final timing = Timing(
  duration: fast ? 1 : 2,
  delay: fast ? 0 : 5,
);
2.7 ms
✓quiet
stays quiet on legacy repetition when the agent edits another line detector.test.ts
write lib/section_header.dart · wrote line 1stayed quiet
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      icon: isArchived
          ? const Icon(Icons.archive)
          : const Icon(Icons.inbox),
      title: isArchived
          ? Text(context.l10n.archivedTitle)
          : Text(context.l10n.inboxTitle),
      subtitle: isArchived
          ? Text(context.l10n.archivedHint)
          : UnreadCount(count: unread),
    );
  }
}
3.2 ms
✓skips
stays quiet when the agent only reads the file detector.test.ts
read lib/section_header.dartno handler of the detector wants this event
/// A message list header.
class SectionHeader extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Header(
      icon: isArchived
          ? const Icon(Icons.archive)
          : const Icon(Icons.inbox),
      title: isArchived
          ? Text(context.l10n.archivedTitle)
          : Text(context.l10n.inboxTitle),
      subtitle: isArchived
          ? Text(context.l10n.archivedHint)
          : UnreadCount(count: unread),
    );
  }
}
0.0 ms
detector.test.tseach detect() call runs the real detectorEdit
import { expect, test } from "vitest";
import { detect, edit, read, write } from "@wellactually/sdk/v3/test";

/** A widget file, so the rule is looking at a widget tree rather than a value object. */
function widgetFile(body: string[]): string {
  return [
    "/// A message list header.",
    "class SectionHeader extends StatelessWidget {",
    "  @override",
    "  Widget build(BuildContext context) {",
    ...body.map((line) => `    ${line}`),
    "  }",
    "}",
    "",
  ].join("\n");
}

const everyProperty = widgetFile([
  "return Header(",
  "  icon: isArchived",
  "      ? const Icon(Icons.archive)",
  "      : const Icon(Icons.inbox),",
  "  title: isArchived",
  "      ? Text(context.l10n.archivedTitle)",
  "      : Text(context.l10n.inboxTitle),",
  "  subtitle: isArchived",
  "      ? Text(context.l10n.archivedHint)",
  "      : UnreadCount(count: unread),",
  ");",
]);

test("fires on the same question at three properties, naming the constructor", () => {
  expect(detect(write("lib/section_header.dart", everyProperty))).toEqual([{ line: 5 }]);
});

test("treats the same question asked from both sides as one question", () => {
  const code = widgetFile([
    "return Header(",
    "  icon: isArchived ? const Icon(Icons.archive) : const Icon(Icons.inbox),",
    "  title: !isArchived ? Text(context.l10n.inbox) : Text(context.l10n.archive),",
    ");",
  ]);
  expect(detect(write("lib/section_header.dart", code))).toEqual([{ line: 5 }]);
});

test("names the widget that holds children repeating a condition", () => {
  const code = widgetFile([
    "return Padding(",
    "  padding: const EdgeInsets.all(8),",
    "  child: Column(",
    "    children: [",
    "      Text(isArchived ? context.l10n.archive : context.l10n.inbox),",
    "      Icon(isArchived ? Icons.archive : Icons.inbox),",
    "    ],",
    "  ),",
    ");",
  ]);
  expect(detect(write("lib/section_header.dart", code))).toEqual([{ line: 7 }]);
});

test("stays quiet on branching the complete widget", () => {
  const code = widgetFile([
    "if (!isArchived) {",
    "  return Header(",
    "    icon: const Icon(Icons.inbox),",
    "    title: Text(context.l10n.inboxTitle),",
    "  );",
    "}",
    "",
    "return Header(",
    "  icon: const Icon(Icons.archive),",
    "  title: Text(context.l10n.archivedTitle),",
    ");",
  ]);
  expect(detect(write("lib/section_header.dart", code))).toEqual([]);
});

const quiet: [string, string[]][] = [
  [
    "independent conditions",
    [
      "return Header(",
      "  icon: isArchived ? const Icon(Icons.archive) : const Icon(Icons.inbox),",
      "  title: hasUnread ? Text(context.l10n.unread) : Text(context.l10n.read),",
      "  trailing: isPinned ? const PinMark() : const SizedBox.shrink(),",
      ");",
    ],
  ],
  [
    "a single varying property",
    ["return Header(", "  icon: isArchived ? const Icon(Icons.archive) : const Icon(Icons.inbox),", "  title: Text(context.l10n.inboxTitle),", ");"],
  ],
  [
    "nullable types, ?? and ?.",
    ["return Header(", "  title: Text(label ?? context.l10n.inboxTitle),", "  subtitle: Text(user?.name ?? fallback ?? other),", "  trailing: Badge(count: counter?.value),", ");"],
  ],
  [
    "two unrelated null checks",
    ["return Header(", "  icon: leading == null ? const Icon(Icons.inbox) : leading,", "  title: label == null ? Text(context.l10n.inboxTitle) : Text(label),", ");"],
  ],
  [
    "callbacks that happen to name their parameter the same",
    [
      "return Column(",
      "  children: [",
      "    Toggle(onChanged: (v) { save(v ? Mode.on : Mode.off); }),",
      "    Toggle(onChanged: (v) { store(v ? Mode.on : Mode.off); }),",
      "  ],",
      ");",
    ],
  ],
  [
    "ternaries in sibling statements",
    ["final label = isArchived ? 'a' : 'b';", "final icon = isArchived ? Icons.archive : Icons.inbox;", "return Text(label);"],
  ],
];

for (const [name, body] of quiet) {
  test(`stays quiet on ${name}`, () => {
    expect(detect(write("lib/section_header.dart", widgetFile(body)))).toEqual([]);
  });
}

test("stays quiet on a value object outside a widget file", () => {
  const code = ["/// Animation timings.", "final timing = Timing(", "  duration: fast ? 1 : 2,", "  delay: fast ? 0 : 5,", ");", ""].join("\n");
  expect(detect(write("lib/timing.dart", code))).toEqual([]);
});

test("stays quiet on legacy repetition when the agent edits another line", () => {
  expect(detect(edit("lib/section_header.dart", everyProperty, { written: "/// A message list header." }))).toEqual([]);
});

test("stays quiet when the agent only reads the file", () => {
  expect(detect(read("lib/section_header.dart", everyProperty))).toEqual([]);
});