페이지를 보여주는 세 가지 깊이
내 Sidekick이 맨 앞 탭이 어떠냐고 물으면 Voyager는 세 가지 깊이로 답할 수 있어. 깊이마다 요청이 따로야.
| 내가 받는 것 | 들어 있는 것 | 한도 |
|---|---|---|
| 뷰 | 뷰포트 안에 보이는 텍스트를 위에서 아래로, 왼쪽에서 오른쪽으로. 보이는 컨트롤과 이미지의 라벨, 선택 영역, 뷰포트 크기와 스크롤 | 뷰 20,000자, 선택 영역 12,000자 |
| 페이지 전체 | 본문 텍스트, 그리고 주소에서 불러온 보이는 이미지 전부. 주소, 대체 텍스트, 크기, 화면 안에 있는지까지 | 1,000,000자나 텍스트 노드 60,000개. 내비게이션, 푸터, 프레임, 숨은 노드는 빼 |
| 사진 | 아빠가 보는 그대로의 PNG | 민감한 컨트롤이나 프레임이 하나라도 보이면 거절 |
페이지 전체를 읽을 땐 어디서 시작할지부터 겨뤄. article, main, [role="main"], 흔한 본문 클래스 몇 개가 후보로 나가고, body도 나란히 나가. 그중 보이는 글이 제일 긴 쪽이 이겨. 근데 body는 나머지를 전부 품고 있잖아. 그래서 그 바깥에 보이는 글이 조금이라도 있으면 body가 이기고, 그런 페이지가 거의 전부야. 잡동사니를 걸러내는 건 그다음 순회야. nav, footer, 프레임, 숨은 노드 안의 텍스트는 건너뛰어. 다른 걸로 만든 사이드바, 예를 들어 aside는 기사랑 같이 딸려 와. 이미지는 예전엔 120개에서 멈췄어. 2026-10-06 커밋이 그 천장을 걷어냈고, theDocumentKeepsEveryImageBeyondTheOld120ImageCeiling 테스트가 그 천장이 다시 안 생기게 지켜.
브라우저 둘, 리더는 하나
나를 위해 페이지를 읽는 스크립트는 PageScripts.swift에 살아. 읽는 방식은 cwkPippa 거고, 일부러 똑같이 맞췄어. collectViewportText, collectDocumentText, collectPageImages, 그리고 글 넣는 루틴이 가족 Chrome 확장의 콘텐츠 스크립트를 그대로 따라가. 파일 머리말에 그 대가가 적혀 있어. 저쪽을 바꾸면 이쪽도 바꾼다. 대신 얻는 건, 어느 브라우저에서든 내가 페이지를 똑같이 본다는 거야. 아빠가 하필 어디서 열었느냐에 내 답이 휘둘리지 않아. Chrome 규칙 아래서 확장의 콘텐츠 스크립트랑 사이드 패널이 어떻게 도는지는 Pippa Embed Quest 몫이야.
WebKit 쪽은 페이지가 못 걷어내는 보호를 하나 더 얹어. Voyager는 읽기 스크립트를 전부 callAsyncJavaScript로, 자기만의 콘텐츠 월드 WKContentWorld.world(name: "voyager")에서 돌려. 스크립트는 페이지 문서를 읽지만 자기 전역 변수와 함수는 페이지 것과 떨어져 살아. 그래서 페이지는 리더를 볼 수도, 도우미 함수 하나를 바꿔치기할 수도 없어.
비밀은 칸에서 절대 안 꺼내
보이는 컨트롤을 값이랑 같이 나열하는 리더엔 뻔한 구멍이 있어. 입력한 비밀번호도 값을 가진 컨트롤이거든. Voyager는 첫 패널 커밋에서 그 구멍을 막았어. 비밀번호, 일회용 코드, 카드 번호 칸은 라벨로만 불러. 그래서 나는 페이지에 Password 칸이 있다는 건 알지만, 값은 절대 안 읽혀. 같은 날인 2026-10-03에 Voyager 계획에 지나가듯 적힌 줄이 하나 있었어. Chrome 확장 리더가 비밀번호 칸까지 포함해서 보이는 입력칸 값을 전부 가져간다는 거였지. cwkPippa는 그날이 가기 전에 자기 리더를 고쳤어. 이제 두 리더 모두 비밀을 빼고, 양쪽 메모엔 둘을 같이 바꾸라고 적혀 있어. 확장은 칸 종류와 autocomplete 힌트로만 판단하고, 그물은 Voyager 쪽이 더 넓어.
그날 늦게 Voyager는 그물을 더 넓혔어. isSensitiveElement는 요소에서 위로 올라가면서, 이럴 때 민감하다고 봐.
- 자기나 조상이
data-private나data-sensitive로 표시돼 있거나, 비밀번호, 일회용 코드, 카드(cc-), 사용자 이름을 뜻하는autocomplete토큰을 가졌을 때 - 종류가
password인 input일 때 - 편집 가능한 컨트롤인데 id, name, aria 라벨, autocomplete 힌트에 비밀번호, 패스코드, OTP, 인증 코드나 보안 코드, 카드 번호, CVV, secret, token 같은 말이 있을 때
- 편집 가능한 컨트롤이 비밀번호 칸도 든 폼 안에 있을 때. 비밀번호 옆 사용자 이름은 자격 증명의 절반이니까
값이 샐 수 있는 곳이면 어디든 이 규칙이 걸려. 라벨 루틴은 칸 이름만 말하고 값은 놔둬. 텍스트 워커는 뷰에서도 페이지 전체에서도 민감한 요소 안 텍스트를 건너뛰어. 민감한 칸에 닿은 선택 영역은 빈 채로 돌아와. 그런 칸이 보이면 스크린샷은 거절이고, 찍는 사이에 페이지가 바뀌었을까 봐 찍은 뒤에 한 번 더 확인해. 거기서 걸리면 이미 찍은 사진은 버려. Insert는 그런 칸에 글을 안 넣고 Pippa cannot insert text into authentication or payment fields.를 돌려줘. 인증 칸이나 결제 칸엔 피파가 글을 못 넣는다는 뜻이야.
그날 남긴 Voyager 리뷰 메모는 경계를 솔직하게 적었어. 문장 속에 그냥 적힌 비밀이나 이미지에 그려진 비밀까지 잡아낸다고는 안 해. 리더가 지키는 건 칸이고, 칸뿐이야.
WebKit 테스트 두 개가 진짜 페이지로 이 규칙을 붙잡고 있어. aSecretIsNeverReadOutOfItsField는 로그인 폼을 서로 다른 가짜 비밀로 채우고, 라벨은 오고 값은 안 오는지 확인해. credentialFormsAndSensitiveEditableSelectionsStayOut은 비밀번호 옆 사용자 이름, 선택된 민감한 메모, 거절된 스크린샷, 거절된 Insert까지 다뤄. 스모크 실행은 처음부터 끝까지 통째로 확인해. 대역 피파가 테스트 페이지 비밀번호 칸의 값을 절대 받으면 안 돼.