with one click
huodongxing-create-event
// Create and manage events on huodongxing.com via browser automation. Use when Ching needs to create a new AI Breakfast event or update an existing one.
// Create and manage events on huodongxing.com via browser automation. Use when Ching needs to create a new AI Breakfast event or update an existing one.
| name | huodongxing-create-event |
| description | Create and manage events on huodongxing.com via browser automation. Use when Ching needs to create a new AI Breakfast event or update an existing one. |
| argument-hint | [event number and details, e.g. '#39 theme: LLM tools'] |
| user-invocable | true |
| allowed-tools | ["Bash","Read","Glob","Write"] |
Create and manage AI Breakfast events on huodongxing.com using agent-browser (Playwright-based browser automation).
agent-browser must be installed--headed modegenerate-posters skill — stored in ~/Projects/youngchingjui/ai-breakfast/events/2026/ai-breakfast-{NUM}/images/graphics/.claude/skills/PREFERENCES.md — check before filling per-event fields--session hdx so state persists across commands--session hdx (e.g. agent-browser --session hdx --headed open https://www.huodongxing.com/login). If the user logs in via the default session, every subsequent --session hdx command sees a logged-out browser and silently redirects to /login.Huodongxing's UI changes silently. Every time you run this skill, watch for behavior that doesn't match these instructions — new validation rules, refs that shift, toasts that flash and disappear, fields that don't carry over between pages. When you finish the workflow, report any new quirks you encountered to Ching at the end so this SKILL.md can be updated. The skill is only as good as the last person who maintained it.
In particular, set up the toast watcher (next section) at the start of every session — it's the single highest-value debugging tool for this site.
Huodongxing fires el-message error toasts for ~1 second then removes them from the DOM. Screenshots almost always miss them. When a button click "does nothing," the page is usually telling you why — but the message is gone before you can capture it.
Set up a MutationObserver right after every agent-browser open (the observer is lost on navigation):
agent-browser --session hdx eval 'window._toastLog = []; var observer = new MutationObserver(function(muts){muts.forEach(function(m){m.addedNodes.forEach(function(n){if(n.nodeType===1 && (n.classList?.contains("el-message") || n.classList?.contains("el-notification") || n.querySelector?.(".el-message__content"))){window._toastLog.push({time:Date.now(),text:(n.textContent||"").trim().substring(0,200),class:n.className})}})})}); observer.observe(document.body, {childList:true, subtree:true}); window._toastObserver = observer; "watching"'
After any action that "didn't work," check the log:
agent-browser --session hdx eval 'JSON.stringify(window._toastLog)'
The create form enforces field order at validation time. Clicking 免费票 or 创建活动 silently triggers validation on EARLIER fields and aborts the action. Fill in this exact order:
请上传活动海报)请设置活动举办地址)请填写活动详情)If a click silently fails, check window._toastLog for the actual reason.
agent-browser --session hdx --headed open https://www.huodongxing.com/login
# Wait for user to log in via WeChat QR or SMS
agent-browser --session hdx get url # verify logged-in state
agent-browser --session hdx open "https://www.huodongxing.com/createv3#/"
# Then re-register the toast watcher (see "Catching Silent Toasts" above)
The "标准模板" (Standard Template) is selected by default — leave it.
agent-browser --session hdx snapshot -i # find the title textbox ref
agent-browser --session hdx fill @e35 "AI Breakfast #XX"
agent-browser --session hdx eval 'document.querySelectorAll("input[type=file]")[0].id = "banner-upload-input"; "set"'
agent-browser --session hdx upload "#banner-upload-input" "/path/to/banner-1080x640.png"
agent-browser --session hdx wait 3000
# A crop dialog appears — confirm:
agent-browser --session hdx eval 'var b = Array.from(document.querySelectorAll("button")).find(function(x){return x.textContent.trim() === "确认" && x.offsetHeight > 0}); if(b){b.click();"clicked"}else"not found"'
The crop dialog may default to a smaller region than 1080×640 (e.g. 972×576). Verify the cropped output matches the design or adjust the crop handles.
Check .claude/skills/PREFERENCES.md for the canonical start/end time. Date pickers are el-date-editor text inputs — set values directly:
agent-browser --session hdx eval 'var inputs = document.querySelectorAll(".el-date-editor input"); ["2026-05-07","2026-05-07"].forEach(function(d,i){inputs[i].value = d; inputs[i].dispatchEvent(new Event("input",{bubbles:true})); inputs[i].dispatchEvent(new Event("change",{bubbles:true}))}); "set"'
If the direct-set approach is rejected (rare), fall back to clicking day cells in the picker popup — that path is more tolerant of validation. Navigate months with the arrow buttons in the popup.
Times use Element UI selects — open by .el-select index, click the option (see "Element UI Selects" pattern below). Index 1 = start time, index 2 = end time. Ref-based access (@e37, @e38) shifts between sessions — always re-snapshot -i or use the index pattern.
Province (index 5) → city (index 6) → detailed address textbox. Use the .el-select index pattern. Address textbox accepts plain inp.value = ...; dispatchEvent("input").
Note: BAKER&SPICE Wheelock Square is in 静安 (Jing'an).
Plain <textarea>, max 150 chars, bilingual. Find by placeholder 请简要概述活动亮点.... See .claude/skills/PREFERENCES.md and recent events for the typical phrasing.
The create page uses TinyMCE; the edit page uses UEditor. If unsure which is loaded:
agent-browser --session hdx eval 'typeof tinymce !== "undefined" ? "tinymce" : typeof UE !== "undefined" ? "UEditor" : "unknown"'
Build the HTML with: event title, date/time, venue, address, agenda/theme (bilingual), and a "free event" note. Then set and save:
agent-browser --session hdx eval 'var html = "<h2>...</h2><p>...</p>"; document.querySelector(".tinymce-wrap.mce-content-body").innerHTML = html; tinymce.activeEditor.save(); "saved, len:" + tinymce.activeEditor.getContent().length'
Use literal Unicode characters (·, –, —, ', ") in HTML, not entity references — UEditor on the edit page double-escapes · → &middot;.
For inserting the poster image, use poster-no-qr.png — huodongxing auto-generates its own QR on the listing page, so the QR-stamped poster is only for WeChat/social sharing. See "UEditor Image Insertion" under Edit Page Differences. On the create page TinyMCE has its own image upload but the simpler pattern is to set innerHTML with an <img src="...vercel-blob..."> directly. As a fallback, the click-driven dialog flow also works: click the 图片 toolbar button → an upload dialog appears with a "+" and a hidden <input type="file"> (index [1] of all file inputs on the page) → set an id on it, agent-browser upload, then click 上传 (scope by button.el-button--success to disambiguate from the banner upload button — see "File uploads" below).
Gotcha: Once inserted, the poster renders at full ~1080px width inside the editor. To reach the ticket/submission sections below, you'll need much larger scroll distances (3000–5000px) than usual.
Clicking 免费票 opens a side drawer (.ticket-drawer inside .el-overlay):
agent-browser --session hdx eval 'document.querySelectorAll(".ticket-form .btn-group button")[0].click(); "clicked"'
agent-browser --session hdx wait 1500
# Drawer inputs: [0]=name, [1]=quantity, [2]=needs-review checkbox
agent-browser --session hdx eval 'var inp = document.querySelectorAll(".ticket-drawer input")[1]; inp.focus(); inp.value = "25"; inp.dispatchEvent(new Event("input",{bubbles:true})); inp.dispatchEvent(new Event("change",{bubbles:true})); inp.value'
agent-browser --session hdx eval 'var btn = Array.from(document.querySelectorAll(".ticket-drawer button")).find(function(b){return b.textContent.trim() === "保存"}); btn.click(); "saved"'
Verify the placeholder is gone:
agent-browser --session hdx eval 'Array.from(document.querySelectorAll("*")).find(function(el){return el.textContent.trim() === "暂无票种"}) ? "still empty" : "ticket added"'
Standard: free ticket, 25 max attendees (see .claude/skills/PREFERENCES.md). Also set the master 总名额 input to 25 (default is 500) — it's a separate cap from the ticket-type quantity. The listing page shows whichever is larger.
The agreement checkbox toggles when clicked — verify state first:
agent-browser --session hdx eval 'document.querySelector(".el-checkbox.is-checked") ? "checked" : "not checked"'
# Only click if "not checked":
agent-browser --session hdx find text "已阅读并同意" click
Then submit:
agent-browser --session hdx eval 'var b = Array.from(document.querySelectorAll("button")).find(function(x){return x.textContent.trim()==="创建活动"}); b.click(); "submitted"'
agent-browser --session hdx wait 5000
agent-browser --session hdx get url
# On success: https://www.huodongxing.com/myevent/home?id=NEW_EVENT_ID
If the URL didn't change, check window._toastLog. As a fallback verification, open the listings page to confirm the new event appears at the top:
agent-browser --session hdx open https://www.huodongxing.com/console/eventadmin
agent-browser --session hdx screenshot /tmp/hdx-verify.png
Element UI dropdowns are NOT real <select> elements. The reliable pattern is the index method:
# 1. Map all selects on the page to find which index is which
agent-browser --session hdx eval 'Array.from(document.querySelectorAll(".el-select")).map(function(s,i){return i+":"+s.textContent.trim().substring(0,30)}).join("|")'
# Output: "0:公开发布|1:09:00|2:18:00|3:线下场地举办|4:中国|5:省份/直辖市|6:城市/地区"
# 2. Open the dropdown by index
agent-browser --session hdx eval 'document.querySelectorAll(".el-select")[6].querySelector("input").click(); "clicked"'
agent-browser --session hdx wait 500
# 3. Click the option from the visible dropdown (use \uXXXX for Chinese)
agent-browser --session hdx eval 'var dd = Array.from(document.querySelectorAll(".el-select-dropdown")).filter(function(d){return d.offsetHeight > 0})[0]; var spans = dd.querySelectorAll(".el-select-dropdown__item span"); var t = Array.from(spans).find(function(s){return s.textContent.trim() === "静安"}); if(t){t.click();"clicked"}else{"not found"}'
Layout reference for the create-page row of selects:
Two hidden file inputs on the create page: [0] = banner, [1] = TinyMCE image. Tag the right one with an ID and use agent-browser upload:
agent-browser --session hdx eval 'document.querySelectorAll("input[type=file]")[1].id = "poster-upload-input"; "set"'
agent-browser --session hdx upload "#poster-upload-input" "/path/to/poster-no-qr.png"
The "上传" button label is ambiguous (banner vs. TinyMCE dialog both use it). find role button click --name "上传" fails with strict-mode violation — use eval to scope by class:
agent-browser --session hdx eval 'var btns = Array.from(document.querySelectorAll("button.el-button--success")); var btn = btns.find(function(b){return b.textContent.trim() === "上传"}); if(btn){btn.click();"clicked"}else{"not found"}'
If agent-browser click @ref returns success but nothing happens, possible causes (in order):
window._toastLogagent-browser eval 'el.scrollIntoView({block:"center"})' then clickos error 35 — retry, or fall back to find text / direct DOM .click()el.__vue__.$parent chain + $forceUpdate()evalShell can mangle straight quotes into curly quotes inside JS strings, causing SyntaxError. Workarounds:
\uXXXX Unicode escapes for Chinese chars instead of literalsMany huodongxing links use target="_blank". After clicking any link, run agent-browser --session hdx tab to check for new tabs and switch into the right one before continuing.
The edit page (/myevent/edit?id=XXX) is a different React/Vue tree from the create page. Key differences:
| Aspect | Create page | Edit page |
|---|---|---|
| Country/province/city | Element UI .el-select | Vue Multiselect .multiselect |
| Rich-text editor | TinyMCE (tinymce.activeEditor) | UEditor (UE.instants[...]) |
| Date inputs | .el-date-editor | flatpickr (el.__vue__.$data.fp) |
| Save button | "创建活动" | "保存活动信息" / "提交" |
| Banner upload | Crop dialog | No crop dialog |
| Layout | Single scroll | Left sidebar nav |
| File inputs | [0]=banner, [1]=TinyMCE | [0]=banner only |
agent-browser --session hdx eval 'var ms = document.querySelectorAll(".multiselect"); Array.from(ms).map(function(s,i){return i+":"+(s.querySelector("input")?.placeholder || "")+"|val:"+s.querySelector(".multiselect__single")?.textContent.trim()}).join("\n")'
The detailed-address textbox (placeholder 请先选择省市,然后输入详细地址) is a regular text input.
The .edui-for-simpleupload button does not respond to programmatic clicks. Workaround: upload the local PNG to a public URL, then setContent with an <img>:
# 1. Upload to Vercel Blob via the poster website's /api/upload (assumes bun dev is running)
curl -s -X POST -F "file=@/path/to/poster-no-qr.png" -F "filename=ai-breakfast-NUM-poster-no-qr.png" http://localhost:3000/api/upload
# Returns: {"url":"https://...vercel-storage.com/...png"}
# 2. Set UEditor content
agent-browser --session hdx eval 'var ed = UE.instants[Object.keys(UE.instants)[0]]; ed.setContent("<p style=\"text-align:center\"><img src=\"" + posterUrl + "\" style=\"max-width:100%\"/></p>" + existingHtml); "set"'
The detailed-address textbox shows empty on the edit page even though it was set on create (country/province/city DID persist). The value is actually still stored server-side and renders correctly on the public listing — but the form field itself is blank. If you save while it's blank, the listing keeps the old address, but if you fill it in and save, the listing updates. After every navigation between create/edit, re-verify all fields are populated.
# Access the flatpickr instance via Vue and set programmatically
agent-browser --session hdx eval 'el.__vue__.$data.fp.setDate("2026-04-09 08:00", true)'
node-Address error on edit submitThe edit page sometimes throws 找不到对应ID的元素: node-Address in the console, which silently blocks submission. Workaround: use the create page (/createv3#/) instead of editing.
Clicking 保存活动信息 on the edit page produces no toast, no modal, no URL change — even when the save succeeds. The toast log will also be empty. The only reliable way to verify a successful save is to reload the edit page and re-read the fields, or open the public listing URL (https://3583937770984.huodongxing.com/event/EVENT_ID) and check that the changes are reflected there.
Events created in the WeChat miniprogram redirect or show a limited editor. Always create new events via the browser if you need full editability.
The QR code generation has a ~10-minute server-side delay. See huodongxing-qr skill — but the rule is: don't request the QR before the 10-min mark, or the server marks it permanently broken.
After the wait:
agent-browser --session hdx open "https://www.huodongxing.com/myevent/promote?id=EVENT_ID&tab=0"
agent-browser --session hdx wait 3000
agent-browser --session hdx eval 'Array.from(document.querySelectorAll("img")).filter(function(i){return (i.src||"").includes("promoteMini")}).map(function(i){return i.src})'
# https://cdn.huodongxing.com/logo/YYYYMM/EVENT_ID/promoteMini.png
curl -sf -H "Referer: https://www.huodongxing.com/" -o /path/to/wechat-mini-program.png "https://cdn.huodongxing.com/logo/YYYYMM/EVENT_ID/promoteMini.png"
file /path/to/wechat-mini-program.png # MUST be: PNG image data, 400 x 400
If the file is ~1KB or HTML, the QR isn't ready — wait longer, do NOT retry rapidly.
~/Projects/youngchingjui/ai-breakfast/events/2026/ai-breakfast-{NUM}/images/
├── graphics/
│ ├── banner-1080x640.png # Huodongxing banner slot
│ ├── poster-no-qr.png # Huodongxing 活动详情 + social sharing
│ └── poster-with-qr.png # WeChat/social sharing
└── qr-codes/
└── wechat-mini-program.png # 400x400, downloaded after 10-min wait
Defaults (venue, time, capacity) are in .claude/skills/PREFERENCES.md. Poster generation lives in the generate-posters skill.
window._toastLog first, then check button is in viewportos error 35 → retry, or use find text / direct DOM .click() instead of @ref\uXXXX escapes for Chinesetinymce.activeEditor.save() after setting innerHTML--session hdx commands redirect to /login → user logged in via the default session; restart the login step with agent-browser --session hdx --headed open https://www.huodongxing.com/login[HINT] Download the complete skill directory including SKILL.md and all related files