Detector: TypeSwitchDetector
Report (why the flagged code is CORRECT and the detector is wrong):
Compiler::travels is a from-source mapper, which this skill's own 'What is NOT this sin' exempts: it turns a domain object into a DIFFERENT wire type — a Signal into an Action, a Component into a Node — and the third arm is a scalar, which can implement no interface. Signal and Component share no hierarchy, so there is no interface to put a per-type method on; the two arms are unrelated types being given their serialised form by the serialiser that owns those forms.
Cleanest design the reporter can conceive:
Exactly what stands: a private mapper on the Compiler matching the value to its wire shape, with the primitive as the default arm. Moving the behaviour onto Signal and Component would make each domain type name its own wire DTO (View\Contracts\Signal knowing View\Values\Action), which inverts the dependency the mapper exception exists to protect AND reinstates the View\Contracts to View\Values namespace cycle that was just removed by moving Confirmable down. Threading the Compiler into the types through a shared Travels interface is worse again: it hands a domain object a back-reference to the compiler that is meant to read it, and still leaves an instanceof for the scalar arm.
⚖️ Maintainer litmus: a valid detector-report needs the flagged code to ALREADY BE the
cleanest design. If the design above differs from the flagged code at all, THAT design is
the owed fix — close this report; the fix is still owed.
Where: src/View/Compiler.php:85
Code (src/View/Compiler.php:85):
82 return $state;
83 }
84
→ 85 private function travels(mixed $value, NodeId $node, string $name): mixed
86 {
87 return match (true)
88 {
89 $value instanceof Signal => Action::raises($value),
90 $value instanceof Component => $this->compileAt($value, $node->under($name, $value, 0)),
91 default => $value,
92 };
93 }
94
95 /**
96 * @return list<Component>
97 */
98 private function composed(Composition $component, NodeId $node): array
99 {
100 $state = $this->states->of($component);
101
102 if ($this->binds($component))
103 {
104 $state = new Bound($state, $this->bindings, $node, $component, self::COMPOSE);
105 }
106
107 $composed = $component::{self::COMPOSE}($state);
108
109 return $composed instanceof Component ? [$composed] : [...$composed];
Where: src/View/Values/Action.php:26
Code (src/View/Values/Action.php:26):
23 * A signal that says it wants asking about carries the question with it, so the press that
24 * would raise it puts that question up first and sends nothing until it is answered.
25 */
→ 26 public static function raises(Signal $signal): self
27 {
28 return new self(
29 ClassAlias::of($signal::class),
30 get_object_vars($signal),
31 $signal instanceof Confirmable ? $signal::confirmation()->toArray() : [],
32 );
33 }
34
35 /**
36 * @return array<string, mixed>
37 */
38 public function jsonSerialize(): array
39 {
40 return array_filter([
41 'signal' => $this->signal,
42 'fields' => $this->fields,
43 'confirmation' => $this->confirmation,
44 ], static fn (mixed $value): bool => $value !== []);
45 }
46 }
Where: src/View/Contracts/Component.php:10
Code (src/View/Contracts/Component.php:10):
7 use JesseGall\Workflows\View\Attributes\Slot;
8
9 abstract class Component
→ 10 {
11 public const string DEFAULT_SLOT = 'default';
12
13 public string | null $key = null;
14
15 #[Slot]
16 public array $children = [];
17
18 public array $attributes = [];
19
20 public function place(Component ...$children): static
21 {
22 $this->children = [...$this->children, ...array_values($children)];
23
24 return $this;
25 }
26
27 /**
28 * @return array<string, list<self>>
29 */
30 public function slots(): array
31 {
32 $slots = [];
33
34 foreach (self::declaredSlots() as $name => $declared)
Filed via commandments report from a consumer project.
Detector:
TypeSwitchDetectorReport (why the flagged code is CORRECT and the detector is wrong):
Compiler::travels is a from-source mapper, which this skill's own 'What is NOT this sin' exempts: it turns a domain object into a DIFFERENT wire type — a Signal into an Action, a Component into a Node — and the third arm is a scalar, which can implement no interface. Signal and Component share no hierarchy, so there is no interface to put a per-type method on; the two arms are unrelated types being given their serialised form by the serialiser that owns those forms.
Cleanest design the reporter can conceive:
Exactly what stands: a private mapper on the Compiler matching the value to its wire shape, with the primitive as the default arm. Moving the behaviour onto Signal and Component would make each domain type name its own wire DTO (View\Contracts\Signal knowing View\Values\Action), which inverts the dependency the mapper exception exists to protect AND reinstates the View\Contracts to View\Values namespace cycle that was just removed by moving Confirmable down. Threading the Compiler into the types through a shared Travels interface is worse again: it hands a domain object a back-reference to the compiler that is meant to read it, and still leaves an instanceof for the scalar arm.
Where:
src/View/Compiler.php:85Code (
src/View/Compiler.php:85):Where:
src/View/Values/Action.php:26Code (
src/View/Values/Action.php:26):Where:
src/View/Contracts/Component.php:10Code (
src/View/Contracts/Component.php:10):Filed via
commandments reportfrom a consumer project.