Skip to content

Commit dbf8db4

Browse files
SoliEstreclaude
andcommitted
fix: intent context prop-down for late-attached provider (ContextRoot + hostConnected)
EstreUI 일반 케이스 버그 — 정적 tile 이 article.onOpen 의 provideIntent 보다 먼저 connect → context-request 유실 → consumer 가 기본 {} intent 만 받고 provider 값 미수신 (prop-down 무음 파손). 강결합 데모 구현 중 발견. fix: (1) consumeIntent/provideIntent 에서 ContextRoot 를 document.body 에 1회 부착(미응답 request 버퍼) (2) plain element host 는 hostConnected 가 자동 호출 안 되므로 provideIntent 가 provider.hostConnected() 수동 호출 → context-provider 재전파 → 먼저 온 consumer 재구독. 회귀 테스트 추가 (consumer→late provider→값 수신). estreuv 39/39, I1 727·I3 4.55KB. 브라우저 검증(Playwright): 사이드바 Inbox 배지·notif unread 가 공유 intent 로 prop-down 동작 확인. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d734a3d commit dbf8db4

3 files changed

Lines changed: 48 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"estreuv": patch
3+
---
4+
5+
Fix intent context for the common EstreUI case where tiles connect **before** the article attaches the provider (`provideIntent` in `onOpen`). A `ContextRoot` now buffers early `context-request`s, and `provideIntent` calls `provider.hostConnected()` so a provider on a plain (non-ReactiveElement) host re-dispatches to already-connected consumers. Previously such consumers kept their default `{}` intent and never received provided values (prop-down silently broken).

packages/estreuv/src/intent-context.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,23 @@
1616
* - default empty intent shape
1717
*/
1818

19-
import { createContext, ContextProvider, ContextConsumer } from '@lit/context';
19+
import { createContext, ContextProvider, ContextConsumer, ContextRoot } from '@lit/context';
20+
21+
/**
22+
* 정적 consumer 가 provider 보다 *먼저* connect 하는 케이스 대비.
23+
* EstreUI article 의 tile 들은 보통 article 이 onOpen 에서 provideIntent 하기 전에
24+
* 이미 DOM 에 있어 context-request 를 먼저 쏜다. ContextRoot 는 미응답 request 를
25+
* 버퍼링했다가 provider 가 나중에 등장하면 재전파한다. 첫 consumer 생성 시
26+
* (connectedCallback 의 request 전) document 에 1회 부착.
27+
*/
28+
let _contextRoot = null;
29+
function ensureContextRoot() {
30+
if (!_contextRoot && typeof document !== 'undefined' && document.body) {
31+
_contextRoot = new ContextRoot();
32+
_contextRoot.attach(document.body);
33+
}
34+
return _contextRoot;
35+
}
2036

2137
/**
2238
* Intent context key. Lit context 시스템 안에서 EstreUI ↔ EstreUV 의 single channel.
@@ -37,7 +53,12 @@ export const intentContext = createContext(Symbol('estreuv:intent'));
3753
* @returns {{ provider: ContextProvider, update: (next: EstreIntent) => void }}
3854
*/
3955
export function provideIntent(host, initial = {}) {
56+
ensureContextRoot();
4057
const provider = new ContextProvider(host, { context: intentContext, initialValue: initial });
58+
// plain element host 는 ReactiveElement 가 아니라 hostConnected 가 자동 호출되지 않음
59+
// → context-provider 이벤트 미발신 → ContextRoot 가 먼저 온 consumer 의 request 를 재전파 못함.
60+
// 수동 호출해 재전파 트리거 (이미 연결된 정적 consumer 들이 늦게 온 provider 에 붙음).
61+
provider.hostConnected?.();
4162
return {
4263
provider,
4364
update(next) {
@@ -65,6 +86,9 @@ export function provideIntent(host, initial = {}) {
6586
* @returns {ContextConsumer}
6687
*/
6788
export function consumeIntent(host, callback) {
89+
// consumer 가 request 를 쏘기 전(생성자/connectedCallback 전)에 ContextRoot 부착 →
90+
// provider 가 나중에 와도 재구독됨 (late-provider 안전).
91+
ensureContextRoot();
6892
return new ContextConsumer(host, {
6993
context: intentContext,
7094
subscribe: true,

packages/estreuv/test/intent-context.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ describe('intent-context', () => {
7373
expect(api.provider.value).toEqual({ step: 'home', darkMode: 'dark' });
7474
});
7575

76+
it('late provider: consumer connected BEFORE provideIntent still receives value (ContextRoot + hostConnected)', async () => {
77+
// EstreUI 의 일반 케이스 — 정적 tile 이 article.onOpen 의 provideIntent 보다 먼저 connect.
78+
let received = [];
79+
class Consumer extends LitElement {
80+
constructor() { super(); this._c = consumeIntent(this, (v) => received.push(v)); }
81+
render() { return ''; }
82+
}
83+
if (!customElements.get('test-intent-consumer-late')) customElements.define('test-intent-consumer-late', Consumer);
84+
const child = document.createElement('test-intent-consumer-late');
85+
host.append(child); // consumer 가 먼저 connect → context-request (provider 아직 없음)
86+
await child.updateComplete;
87+
// 이제 provider 를 LATE 부착 (article.onOpen 시점 모사)
88+
const api = provideIntent(host, { step: 'home', counts: { Inbox: 3 } });
89+
await child.updateComplete;
90+
expect(api.provider.value).toEqual({ step: 'home', counts: { Inbox: 3 } });
91+
expect(received[received.length - 1]).toEqual({ step: 'home', counts: { Inbox: 3 } });
92+
});
93+
7694
it('intentContext is a stable context key', () => {
7795
// @lit/context createContext(key) returns the key itself — here a unique Symbol
7896
expect(intentContext).toBeTruthy();

0 commit comments

Comments
 (0)