@pencil-agent/nano-pencil 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/README.md +267 -267
  2. package/dist/build-meta.json +3 -3
  3. package/dist/core/export-html/AGENT.md +11 -11
  4. package/dist/core/export-html/template.css +971 -971
  5. package/dist/core/export-html/template.html +54 -54
  6. package/dist/core/model/custom-providers.js +1 -1
  7. package/dist/core/model-registry.js +5 -5
  8. package/dist/extensions/builtin/AGENT.md +115 -115
  9. package/dist/extensions/builtin/browser/AGENT.md +17 -17
  10. package/dist/extensions/builtin/browser/agent-workspace/agent_helpers.py +12 -12
  11. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/amazon/product-search.md +198 -198
  12. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/archive-org/scraping.md +341 -341
  13. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv/scraping.md +311 -311
  14. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv-bulk/scraping.md +333 -333
  15. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/atlas/overview.md +70 -70
  16. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/booking-com/scraping.md +578 -578
  17. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/capterra/scraping.md +440 -440
  18. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/centilebrain/generate-estimates.md +110 -110
  19. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coingecko/scraping.md +325 -325
  20. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coinmarketcap/scraping.md +463 -463
  21. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coursera/scraping.md +360 -360
  22. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/craigslist/scraping.md +390 -390
  23. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/crossref/scraping.md +568 -568
  24. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/dev-to/scraping.md +323 -323
  25. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/duckduckgo/scraping.md +349 -349
  26. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/ebay/scraping.md +435 -435
  27. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/etsy/scraping.md +506 -506
  28. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/eventbrite/scraping.md +363 -363
  29. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/expedia/automation.md +168 -168
  30. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/groups.md +236 -236
  31. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/pages.md +295 -295
  32. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/framer/editor.md +108 -108
  33. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/fred/scraping.md +493 -493
  34. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/g2/scraping.md +580 -580
  35. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/genius/scraping.md +511 -511
  36. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/repo-actions.md +65 -65
  37. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/scraping.md +184 -184
  38. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/glassdoor/scraping.md +543 -543
  39. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gmail/compose.md +122 -122
  40. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/goodreads/scraping.md +461 -461
  41. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gutenberg/scraping.md +383 -383
  42. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/hackernews/scraping.md +243 -243
  43. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/howlongtobeat/scraping.md +473 -473
  44. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/imdb/scraping.md +271 -271
  45. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/itch-io/scraping.md +436 -436
  46. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/job-boards/indeed-glassdoor.md +1021 -1021
  47. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/letterboxd/scraping.md +349 -349
  48. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/linkedin/invitation-manager.md +109 -109
  49. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/loom/folder-enumeration.md +170 -170
  50. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/macrotrends/scraping.md +537 -537
  51. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/article-hydration.md +120 -120
  52. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/scraping.md +414 -414
  53. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/metacritic/scraping.md +477 -477
  54. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/musicbrainz/scraping.md +478 -478
  55. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/nasa/scraping.md +339 -339
  56. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/news-aggregation/multi-source.md +205 -205
  57. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/open-library/scraping.md +472 -472
  58. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openalex/scraping.md +470 -470
  59. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openstreetmap/scraping.md +490 -490
  60. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/package-registries/npm-pypi.md +478 -478
  61. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/polymarket/scraping.md +234 -234
  62. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/producthunt/scraping.md +307 -307
  63. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/pubmed/scraping.md +421 -421
  64. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/quora/scraping.md +364 -364
  65. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rawg/scraping.md +352 -352
  66. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/reddit/scraping.md +124 -124
  67. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rest-countries/scraping.md +233 -233
  68. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/sec-edgar/scraping.md +361 -361
  69. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/README.md +36 -36
  70. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/embedded-apps.md +72 -72
  71. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/knowledge-base.md +109 -109
  72. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/polaris-inputs.md +137 -137
  73. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/soundcloud/scraping.md +362 -362
  74. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/spotify/scraping.md +339 -339
  75. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/stackoverflow/scraping.md +435 -435
  76. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/steam/scraping.md +575 -575
  77. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/substack/scraping.md +338 -338
  78. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md +52 -52
  79. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tiktok/upload.md +107 -107
  80. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tradingview/scraping.md +309 -309
  81. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trello/boards-and-lists.md +88 -88
  82. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trustpilot/scraping.md +375 -375
  83. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/walmart/scraping.md +444 -444
  84. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wayback-machine/scraping.md +306 -306
  85. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/weather/scraping.md +398 -398
  86. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wellfound/scraping.md +596 -596
  87. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/world-bank/scraping.md +356 -356
  88. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/xiaohongshu/scraping.md +84 -84
  89. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/youtube/scraping.md +418 -418
  90. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/zillow/scraping.md +433 -433
  91. package/dist/extensions/builtin/browser/browser.md +73 -73
  92. package/dist/extensions/builtin/browser/install.md +142 -142
  93. package/dist/extensions/builtin/browser/interaction-skills/connection.md +48 -48
  94. package/dist/extensions/builtin/browser/interaction-skills/cookies.md +3 -3
  95. package/dist/extensions/builtin/browser/interaction-skills/cross-origin-iframes.md +3 -3
  96. package/dist/extensions/builtin/browser/interaction-skills/dialogs.md +64 -64
  97. package/dist/extensions/builtin/browser/interaction-skills/downloads.md +3 -3
  98. package/dist/extensions/builtin/browser/interaction-skills/drag-and-drop.md +3 -3
  99. package/dist/extensions/builtin/browser/interaction-skills/dropdowns.md +3 -3
  100. package/dist/extensions/builtin/browser/interaction-skills/iframes.md +3 -3
  101. package/dist/extensions/builtin/browser/interaction-skills/network-requests.md +3 -3
  102. package/dist/extensions/builtin/browser/interaction-skills/print-as-pdf.md +3 -3
  103. package/dist/extensions/builtin/browser/interaction-skills/profile-sync.md +90 -90
  104. package/dist/extensions/builtin/browser/interaction-skills/screenshots.md +17 -17
  105. package/dist/extensions/builtin/browser/interaction-skills/scrolling.md +3 -3
  106. package/dist/extensions/builtin/browser/interaction-skills/shadow-dom.md +3 -3
  107. package/dist/extensions/builtin/browser/interaction-skills/tabs.md +69 -69
  108. package/dist/extensions/builtin/browser/interaction-skills/uploads.md +1 -1
  109. package/dist/extensions/builtin/browser/interaction-skills/viewport.md +3 -3
  110. package/dist/extensions/builtin/browser/src/browser_harness/AGENT.md +15 -15
  111. package/dist/extensions/builtin/browser/src/browser_harness/__init__.py +8 -8
  112. package/dist/extensions/builtin/browser/src/browser_harness/_ipc.py +90 -90
  113. package/dist/extensions/builtin/browser/src/browser_harness/admin.py +722 -722
  114. package/dist/extensions/builtin/browser/src/browser_harness/daemon.py +328 -328
  115. package/dist/extensions/builtin/browser/src/browser_harness/helpers.py +396 -396
  116. package/dist/extensions/builtin/browser/src/browser_harness/run.py +103 -103
  117. package/dist/extensions/builtin/debug/index.js +9 -9
  118. package/dist/extensions/builtin/discipline/skills/brainstorming/SKILL.md +33 -33
  119. package/dist/extensions/builtin/discipline/skills/executing-plans/SKILL.md +25 -25
  120. package/dist/extensions/builtin/discipline/skills/finishing-development-branch/SKILL.md +25 -25
  121. package/dist/extensions/builtin/discipline/skills/receiving-code-review/SKILL.md +22 -22
  122. package/dist/extensions/builtin/discipline/skills/requesting-code-review/SKILL.md +31 -31
  123. package/dist/extensions/builtin/discipline/skills/systematic-debugging/SKILL.md +28 -28
  124. package/dist/extensions/builtin/discipline/skills/test-driven-development/SKILL.md +32 -32
  125. package/dist/extensions/builtin/discipline/skills/using-git-worktrees/SKILL.md +25 -25
  126. package/dist/extensions/builtin/discipline/skills/verification-before-completion/SKILL.md +27 -27
  127. package/dist/extensions/builtin/discipline/skills/writing-plans/SKILL.md +26 -26
  128. package/dist/extensions/builtin/goal/README.md +67 -67
  129. package/dist/extensions/builtin/goal/index.js +6 -6
  130. package/dist/extensions/builtin/grub/README.md +112 -112
  131. package/dist/extensions/builtin/link-world/agent-workspace/README.md +16 -16
  132. package/dist/extensions/builtin/link-world/internet-search/internet-search.md +65 -65
  133. package/dist/extensions/builtin/link-world/link-world-agent.md +82 -82
  134. package/dist/extensions/builtin/link-world/linkworld.md +313 -313
  135. package/dist/extensions/builtin/link-world/network-routing/network-routing.md +67 -67
  136. package/dist/extensions/builtin/loop/README.md +92 -92
  137. package/dist/extensions/builtin/mcp/figma-design.md +68 -68
  138. package/dist/extensions/builtin/mcp/mcp-management.md +85 -85
  139. package/dist/extensions/builtin/recap/AGENT.md +15 -15
  140. package/dist/extensions/builtin/sal/README.md +72 -72
  141. package/dist/extensions/builtin/security-audit/README.md +289 -289
  142. package/dist/extensions/builtin/team/AGENT.md +112 -112
  143. package/dist/extensions/builtin/team/TESTING.md +299 -299
  144. package/dist/extensions/builtin/token-save/README.md +56 -56
  145. package/dist/extensions/optional/AGENT.md +10 -10
  146. package/dist/modes/interactive/controllers/input-submit-controller.js +2 -2
  147. package/dist/modes/interactive/controllers/stream-render-controller.js +2 -2
  148. package/dist/modes/interactive/interactive-mode.js +19 -19
  149. package/dist/modes/interactive/theme/dark.json +85 -85
  150. package/dist/modes/interactive/theme/light.json +84 -84
  151. package/dist/modes/interactive/theme/theme-schema.json +335 -335
  152. package/dist/modes/interactive/theme/warm.json +81 -81
  153. package/dist/node_modules/@pencil-agent/ai/dist/cli.js +0 -0
  154. package/dist/node_modules/@pencil-agent/ai/dist/models.generated.js +1 -1
  155. package/docs/ACP/345/215/217/350/256/256/351/233/206/346/210/220/345/274/200/345/217/221/346/226/207/346/241/243.md +851 -0
  156. package/docs/SDK-TESTING.md +364 -0
  157. package/docs/codex-goal-command-impl.md +1055 -1055
  158. package/docs/codex-goal-vs-grub.md +500 -500
  159. package/docs/custom-provider.md +27 -27
  160. package/docs/extensions.md +27 -27
  161. package/docs/keybindings.md +27 -27
  162. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +250 -250
  163. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +122 -122
  164. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -1222
  165. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +158 -158
  166. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +128 -128
  167. package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +320 -320
  168. package/docs/loop-usage-examples.md +214 -214
  169. package/docs/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +593 -0
  170. package/docs/models.md +27 -27
  171. package/docs/packages.md +27 -27
  172. package/docs/pi-design-philosophy.md +457 -457
  173. package/docs/planmode.md +1987 -1987
  174. package/docs/prompt-templates.md +27 -27
  175. package/docs/providers.md +27 -27
  176. package/docs/sdk.md +27 -27
  177. package/docs/skills.md +27 -27
  178. package/docs/startup-performance-optimization.md +301 -0
  179. package/docs/themes.md +27 -27
  180. package/docs/tui.md +27 -27
  181. package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +47 -0
  182. package/package.json +190 -190
  183. package/docs/cc-agent-design.md +0 -1297
  184. package/docs/cc-tui-design.md +0 -1333
  185. package/docs/nanoPencil-/345/255/246/344/271/240/350/256/241/345/210/222.md +0 -170
  186. package/docs/scan-report.md +0 -3820
  187. package/docs//345/257/271/346/240/207Claude-Code.md +0 -1775
  188. package/docs//351/230/277/351/207/214/345/267/264/345/267/264/350/264/242/346/212/245/345/210/206/346/236/220/344/271/246.md +0 -261
@@ -1,168 +1,168 @@
1
- # Expedia — Browser Automation
2
-
3
- Field-tested against expedia.co.in on 2026-04-27 using `browser-harness` CDP
4
- helpers (`goto`, `js`, `click`, `type_text`, `screenshot`).
5
-
6
- ---
7
-
8
- ## TL;DR
9
-
10
- **Build your search via URL parameters, not the UI.** The date picker, destination
11
- autocomplete, and traveller widgets are fragile—coordinate clicks frequently
12
- dismiss them or mis-target. Encode everything you can into a `goto()` URL, then
13
- use JS clicks only for what the URL can't express (child ages, price filters).
14
-
15
- ---
16
-
17
- ## Hotel Search URL Template
18
-
19
- ```
20
- https://www.expedia.co.in/Hotel-Search?destination={DEST}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}&rooms={N}&adults={N}&children={N}&childrenAges={age1,age2,...}
21
- ```
22
-
23
- Example — 2 adults, 2 children (ages 5 and 7), Tokyo, June 2026:
24
-
25
- ```python
26
- goto("https://www.expedia.co.in/Hotel-Search?"
27
- "destination=Central+Tokyo,+Tokyo+Prefecture&"
28
- "startDate=2026-06-01&endDate=2026-06-07&"
29
- "rooms=1&adults=2&children=2&childrenAges=5,7")
30
- ```
31
-
32
- **Note:** `childrenAges` in the URL may not always populate the age dropdowns on
33
- the results page. Verify with a screenshot and set them via JS if needed.
34
-
35
- ---
36
-
37
- ## Date Picker — DO NOT USE
38
-
39
- The calendar widget is extremely unreliable with coordinate-based clicks:
40
-
41
- - Clicking a date cell frequently **closes the entire picker** instead of
42
- selecting the date.
43
- - The picker has month-navigation arrows that are tiny targets.
44
- - "Flexible dates" mode has a different DOM structure with pill-shaped month
45
- selectors that also mis-fire.
46
- - Dozens of retry attempts across multiple strategies all failed.
47
-
48
- **Workaround:** Always pass dates via URL parameters (`startDate`, `endDate`).
49
-
50
- ---
51
-
52
- ## Travellers Widget
53
-
54
- The travellers stepper panel works with JS `.click()` on the increment/decrement
55
- buttons.
56
-
57
- ### Opening the panel
58
-
59
- ```python
60
- js("""
61
- (()=>{
62
- let btn = document.querySelector('button[data-testid="travelers-field-trigger"]')
63
- || [...document.querySelectorAll('button')].find(b => b.textContent.includes('traveller'));
64
- if(btn){ btn.click(); return 'opened'; }
65
- return 'not found';
66
- })()
67
- """)
68
- ```
69
-
70
- ### Incrementing children count
71
-
72
- ```python
73
- js("""
74
- (()=>{
75
- let span = [...document.querySelectorAll('span')].find(s => s.textContent.trim() === 'Children');
76
- if(!span) return 'no Children label';
77
- let container = span.closest('div').parentElement;
78
- let buttons = container.querySelectorAll('button');
79
- let plus = buttons[buttons.length - 1]; // last button is "+"
80
- plus.click();
81
- return 'incremented';
82
- })()
83
- """)
84
- ```
85
-
86
- ### Setting child ages
87
-
88
- Child age dropdowns are `<select>` elements with `aria-label` like
89
- "Child 1 age", "Child 2 age", etc.
90
-
91
- ```python
92
- js("""
93
- (()=>{
94
- let selects = document.querySelectorAll('select');
95
- // selects[0] = Child 1 age, selects[1] = Child 2 age, etc.
96
- let setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
97
- selects[0].value = '5';
98
- selects[0].dispatchEvent(new Event('change', {bubbles:true}));
99
- selects[1].value = '7';
100
- selects[1].dispatchEvent(new Event('change', {bubbles:true}));
101
- return 'ages set';
102
- })()
103
- """)
104
- ```
105
-
106
- ### Closing the panel
107
-
108
- ```python
109
- js("""
110
- (()=>{
111
- let btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim() === 'Done');
112
- if(btn){ btn.click(); return 'done'; }
113
- return 'no Done button';
114
- })()
115
- """)
116
- ```
117
-
118
- ---
119
-
120
- ## Price Filter
121
-
122
- On the results page, the nightly-price filter has two text inputs and two range
123
- sliders.
124
-
125
- | Element | Selector |
126
- |---------|----------|
127
- | Min text input | `#price-min` |
128
- | Max text input | `#price-max` |
129
- | Min range slider | `input[type="range"][aria-label*="Minimum"]` |
130
- | Max range slider | `input[type="range"][aria-label*="Maximum"]` |
131
-
132
- ### Setting max price
133
-
134
- The most reliable method is to click the input, select all, type the value, and
135
- press Enter:
136
-
137
- ```python
138
- click(x, y) # coordinates of #price-max
139
- js("document.getElementById('price-max').select()")
140
- type_text("20000")
141
- press_key("Enter")
142
- ```
143
-
144
- Setting the value purely via JS (`dispatchEvent`) does trigger a re-search but
145
- coordinate-click + type is more reliable for actually applying the filter.
146
-
147
- ---
148
-
149
- ## Key Lessons
150
-
151
- 1. **URL-first strategy** — Encode destination, dates, room count, adults,
152
- children, and child ages in the URL. Only use UI interaction for things the
153
- URL cannot express.
154
-
155
- 2. **JS `.click()` over coordinate clicks** — For buttons inside panels
156
- (traveller stepper, Done), find elements by text/attribute and call `.click()`
157
- in JS. Coordinate clicks on overlay panels are unreliable.
158
-
159
- 3. **`dispatchEvent` with `{bubbles: true}`** — Required for React-controlled
160
- inputs (selects, text fields). Without bubbling, React state won't update.
161
-
162
- 4. **Wait after navigation** — After `goto()` or pressing Search, call
163
- `wait_for_load()` + `time.sleep(3)` before interacting. Expedia loads
164
- results asynchronously.
165
-
166
- 5. **Indian locale** — `expedia.co.in` shows prices in ₹ (INR). The price
167
- filter values include the ₹ symbol and commas in the text input but the
168
- underlying range slider uses plain integers.
1
+ # Expedia — Browser Automation
2
+
3
+ Field-tested against expedia.co.in on 2026-04-27 using `browser-harness` CDP
4
+ helpers (`goto`, `js`, `click`, `type_text`, `screenshot`).
5
+
6
+ ---
7
+
8
+ ## TL;DR
9
+
10
+ **Build your search via URL parameters, not the UI.** The date picker, destination
11
+ autocomplete, and traveller widgets are fragile—coordinate clicks frequently
12
+ dismiss them or mis-target. Encode everything you can into a `goto()` URL, then
13
+ use JS clicks only for what the URL can't express (child ages, price filters).
14
+
15
+ ---
16
+
17
+ ## Hotel Search URL Template
18
+
19
+ ```
20
+ https://www.expedia.co.in/Hotel-Search?destination={DEST}&startDate={YYYY-MM-DD}&endDate={YYYY-MM-DD}&rooms={N}&adults={N}&children={N}&childrenAges={age1,age2,...}
21
+ ```
22
+
23
+ Example — 2 adults, 2 children (ages 5 and 7), Tokyo, June 2026:
24
+
25
+ ```python
26
+ goto("https://www.expedia.co.in/Hotel-Search?"
27
+ "destination=Central+Tokyo,+Tokyo+Prefecture&"
28
+ "startDate=2026-06-01&endDate=2026-06-07&"
29
+ "rooms=1&adults=2&children=2&childrenAges=5,7")
30
+ ```
31
+
32
+ **Note:** `childrenAges` in the URL may not always populate the age dropdowns on
33
+ the results page. Verify with a screenshot and set them via JS if needed.
34
+
35
+ ---
36
+
37
+ ## Date Picker — DO NOT USE
38
+
39
+ The calendar widget is extremely unreliable with coordinate-based clicks:
40
+
41
+ - Clicking a date cell frequently **closes the entire picker** instead of
42
+ selecting the date.
43
+ - The picker has month-navigation arrows that are tiny targets.
44
+ - "Flexible dates" mode has a different DOM structure with pill-shaped month
45
+ selectors that also mis-fire.
46
+ - Dozens of retry attempts across multiple strategies all failed.
47
+
48
+ **Workaround:** Always pass dates via URL parameters (`startDate`, `endDate`).
49
+
50
+ ---
51
+
52
+ ## Travellers Widget
53
+
54
+ The travellers stepper panel works with JS `.click()` on the increment/decrement
55
+ buttons.
56
+
57
+ ### Opening the panel
58
+
59
+ ```python
60
+ js("""
61
+ (()=>{
62
+ let btn = document.querySelector('button[data-testid="travelers-field-trigger"]')
63
+ || [...document.querySelectorAll('button')].find(b => b.textContent.includes('traveller'));
64
+ if(btn){ btn.click(); return 'opened'; }
65
+ return 'not found';
66
+ })()
67
+ """)
68
+ ```
69
+
70
+ ### Incrementing children count
71
+
72
+ ```python
73
+ js("""
74
+ (()=>{
75
+ let span = [...document.querySelectorAll('span')].find(s => s.textContent.trim() === 'Children');
76
+ if(!span) return 'no Children label';
77
+ let container = span.closest('div').parentElement;
78
+ let buttons = container.querySelectorAll('button');
79
+ let plus = buttons[buttons.length - 1]; // last button is "+"
80
+ plus.click();
81
+ return 'incremented';
82
+ })()
83
+ """)
84
+ ```
85
+
86
+ ### Setting child ages
87
+
88
+ Child age dropdowns are `<select>` elements with `aria-label` like
89
+ "Child 1 age", "Child 2 age", etc.
90
+
91
+ ```python
92
+ js("""
93
+ (()=>{
94
+ let selects = document.querySelectorAll('select');
95
+ // selects[0] = Child 1 age, selects[1] = Child 2 age, etc.
96
+ let setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
97
+ selects[0].value = '5';
98
+ selects[0].dispatchEvent(new Event('change', {bubbles:true}));
99
+ selects[1].value = '7';
100
+ selects[1].dispatchEvent(new Event('change', {bubbles:true}));
101
+ return 'ages set';
102
+ })()
103
+ """)
104
+ ```
105
+
106
+ ### Closing the panel
107
+
108
+ ```python
109
+ js("""
110
+ (()=>{
111
+ let btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim() === 'Done');
112
+ if(btn){ btn.click(); return 'done'; }
113
+ return 'no Done button';
114
+ })()
115
+ """)
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Price Filter
121
+
122
+ On the results page, the nightly-price filter has two text inputs and two range
123
+ sliders.
124
+
125
+ | Element | Selector |
126
+ |---------|----------|
127
+ | Min text input | `#price-min` |
128
+ | Max text input | `#price-max` |
129
+ | Min range slider | `input[type="range"][aria-label*="Minimum"]` |
130
+ | Max range slider | `input[type="range"][aria-label*="Maximum"]` |
131
+
132
+ ### Setting max price
133
+
134
+ The most reliable method is to click the input, select all, type the value, and
135
+ press Enter:
136
+
137
+ ```python
138
+ click(x, y) # coordinates of #price-max
139
+ js("document.getElementById('price-max').select()")
140
+ type_text("20000")
141
+ press_key("Enter")
142
+ ```
143
+
144
+ Setting the value purely via JS (`dispatchEvent`) does trigger a re-search but
145
+ coordinate-click + type is more reliable for actually applying the filter.
146
+
147
+ ---
148
+
149
+ ## Key Lessons
150
+
151
+ 1. **URL-first strategy** — Encode destination, dates, room count, adults,
152
+ children, and child ages in the URL. Only use UI interaction for things the
153
+ URL cannot express.
154
+
155
+ 2. **JS `.click()` over coordinate clicks** — For buttons inside panels
156
+ (traveller stepper, Done), find elements by text/attribute and call `.click()`
157
+ in JS. Coordinate clicks on overlay panels are unreliable.
158
+
159
+ 3. **`dispatchEvent` with `{bubbles: true}`** — Required for React-controlled
160
+ inputs (selects, text fields). Without bubbling, React state won't update.
161
+
162
+ 4. **Wait after navigation** — After `goto()` or pressing Search, call
163
+ `wait_for_load()` + `time.sleep(3)` before interacting. Expedia loads
164
+ results asynchronously.
165
+
166
+ 5. **Indian locale** — `expedia.co.in` shows prices in ₹ (INR). The price
167
+ filter values include the ₹ symbol and commas in the text input but the
168
+ underlying range slider uses plain integers.