fix(M02): 단가산출 다이어그램 — 함수 상자 몸통 · 「+」 받는 자리에 놓으면 보낸 알약 이름으로 on.link(PLAN 35-7)

- 받는 알약 0 개인 새 함수 상자도 이을 수 있음 · 받는 알약은 Model 이 새로 만듦
- 함수 상자 왼쪽 흐린 「+」 받는 자리 · 잇기 중이면 밝게
- ORCA: 빈 함수 상자 몸통 ← 면적 · 「+」 ← 높이 → on.link 두 번(이름 그대로) · CostFlow 전부 검사 초록

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z
This commit is contained in:
2026-10-01 16:22:01 +09:00
co-authored by Claude Opus 5.5
parent 97b7111c06
commit 71c20453e4
2 changed files with 54 additions and 1 deletions
@@ -256,3 +256,21 @@
stroke-width: 2;
stroke-dasharray: 5 4;
}
/* 함수 상자 흐린 「+」 받는 자리 */
.m02-cfdg__pill.is-plus {
justify-content: center;
min-width: 2rem;
border-style: dashed;
border-color: var(--color-text-muted);
background: transparent;
color: var(--color-text-muted);
opacity: 0.7;
}
.m02-cfdg__pill.is-plus:hover,
.m02-cfdg.is-linking .m02-cfdg__pill.is-plus.is-target {
opacity: 1;
border-color: var(--cfdg-in);
color: var(--cfdg-in);
}
+36 -1
View File
@@ -53,6 +53,10 @@ const TEXT = {
From: ["어디서", "From"],
To: ["어디로", "To"],
Value: ["지금 값", "Now"],
PlusIn: [
"받는 자리 — 내보내는 알약을 여기에 놓으면 받는 알약이 생김",
"Drop an output pill here to add an input",
],
LineOf: ["선 · {a} → {b}", "Line · {a} → {b}"],
Hint: [
"상자 머리를 끌어 옮김 · 빈 곳 끌어 판 옮김 · 휠로 확대",
@@ -124,6 +128,9 @@ export function resolveDrop(
if (target.box === from.box) return { why: tt("SameBox") };
if (target.dir === "out") return { why: tt("NotIn") };
const box = lg.boxes.find((b) => b.id === target.box);
// 함수 상자 몸통 · 「+」 받는 자리 — 보낸 알약 이름으로 받는 알약을 새로(Model 이 만들고 이름이 겹치면 「이름 (2)」)
if (box?.kind === "function" && target.name === null)
return { to: { box: box.id, name: from.name } };
const ins = box ? pillsOf(box).ins : [];
if (!box || !ins.length) return { why: tt("NoIn") };
const name =
@@ -300,6 +307,28 @@ export function renderDiagram(
return btn;
};
/** 함수 상자 왼쪽 흐린 「+」 — 여기(또는 몸통)에 놓으면 받는 알약이 새로 생김 */
const plusSlot = (b: CfBox): HTMLElement => {
const slot = el("button", {
className: `m02-cfdg__pill is-in is-plus${st.linking && st.linking.box !== b.id ? " is-target" : ""}`,
text: "+",
attrs: {
type: "button",
"data-box": b.id,
"data-dir": "in",
"data-plus": "1",
title: tt("PlusIn"),
},
});
slot.addEventListener("click", (ev) => {
ev.stopPropagation();
if (st.linking) return tryLink(st.linking, { box: b.id, name: null, dir: "in" });
st.note = tt("PlusIn");
redraw();
});
return slot;
};
const boxEl = (b: CfBox): HTMLElement => {
const { ins, outs } = pillsOf(b);
const head = el("div", {
@@ -323,7 +352,13 @@ export function renderDiagram(
el("div", {
className: "m02-cfdg__pills",
children: [
el("div", { className: "m02-cfdg__ins", children: ins.map((p) => pillEl(b, p, "in")) }),
el("div", {
className: "m02-cfdg__ins",
children: [
...ins.map((p) => pillEl(b, p, "in")),
...(b.kind === "function" ? [plusSlot(b)] : []),
],
}),
el("div", {
className: "m02-cfdg__outs",
children: outs.map((p) => pillEl(b, p, "out")),