feat: add session search filters (#94)#172
Conversation
44bf581 to
6b53b41
Compare
There was a problem hiding this comment.
Pull request overview
This PR adds room/tag/type multi-select filters and a full-text search input to the session schedule table on the /session page, plus three new shared UI primitives (CpButton, CpTextField, CpCheckbox) and a CpSessionFilterDropdown feature component. It keeps the existing CpSessionItem/CpBadge visuals intact and renders only room columns that still have matching sessions, with a localized empty state.
Changes:
- Add a filter toolbar above the schedule with two multi-select dropdowns (rooms, tags/types) and a search box; wire OR-filtering and localized case-insensitive search across title/description/speaker name/speaker id.
- Derive room options from schedulable sessions using the Pretalx
envalue as a stable id, and union submission type (with azh\0encomposite id) into tag options so localized switching keeps identity. - Introduce
CpButton,CpTextField,CpCheckbox, andCpSessionFilterDropdownbuilt with UnoCSS utility classes; restrict rendered room columns to those with matching sessions and show a localized "no results" message.
Reviewed changes
Copilot reviewed 5 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
app/components/feature/CpSessionTable.vue |
Adds filter/search state, option derivation, matching logic, restricted rooms, empty state, and i18n strings; preserves the original grid rendering. |
app/components/feature/CpSessionFilterDropdown.vue |
New dropdown with search, checkbox list, selected-label trigger with clear button, and click-outside dismissal. |
app/components/shared/CpButton.vue |
New button primitive with primary/secondary/basic variants and responsive sizing. |
app/components/shared/CpTextField.vue |
New text input primitive with prefix icon slot and clear button. |
app/components/shared/CpCheckbox.vue |
New checkbox primitive with larger mobile tap target. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
6b53b41 to
bac9d11
Compare
Dokploy Preview Deployment
|
|
docs/pr 稍後刪除,那是 Codex 無法 attach 附件的 workaround。不影響其他程式碼的 review~ |
|
哦,我剛剛才注意到這則留言,不好意思,應該是我忘記這回事了 🥲 不過我想影響不大,實際原因我想和你分享一下我最近正在嘗試的新解決方案:「讓每個 PR 都各自獨立」。 一般來說,以「議程表」這個大項來說,無論是手機版列表、篩選功能,還是我正在做的收藏功能,這三個都應該要是互相獨立的。我們理論上應該要可以合併其中一個都有共識的 PR(A or B or C),而不是必須等到「手機版列表」完成後,才能合併「篩選功能」,然後再來合併「收藏功能」(A → B → C)。但與此同時,我們又希望這三個 PR 都去共用類似的元件,避免造成一個 PR 合併之後,另外一個 PR 要經過大幅度的重寫,才能去配合最新 除此之外,COSCUP 的開發通常是以非同步的方式進行。就以 #169 這個 PR 來說,雖然這個 PR 已經進
也因此,我決定試試看不去 stack 任何 PR,而是改去降低多個 PR 間合併的 conflict 解決成本。我在 #160 的 Plan (對應的 PR 是 #176) 中,補了這麼一段 prompt:
這樣一來,Claude Code 就會盡量複用既有的設計,就算設計需要更改,也可以避免元件撞名(而 merge conflict)的問題。舉例來說,#172 和 #176 都需要 Tip 理論上這樣 main 會出現很多重複的 components。DRY 的重構任務(比如把 當然對於主元件 所以如果就以上面的理論來講,其實我的 PR 應該是不需要 stack 你的 PR,你可以把他當作各自獨立的功能來 ship,來加速終端使用者看到新功能的速度。如果任一 PR 合併了,我會再讓我的 Claude Code 去修正其他我自己的 PR,符合目前 main 的設計(不過我還沒有試過其中一個功能合併後,另外幾個 PR 的衝突解決難度,這部份可以一起試試看)。
至於第二點的話,我同意你的觀點,這部分會修正看看~但在實作第二點之前,我想先看看你對於第一點的看法,來決定我要不要先改變基礎分支。 |
|
我認為 Stack PR 除了方便 Review 之外,還有另一個價值是能夠明確表達功能之間的演進關係。後續參與開發的人可以透過 PR Chain 了解目前的開發方向,以及哪些設計已經確定、哪些部分仍在調整。對於高度相關的功能來說,這有助於開發者掌握彼此的實作方式與設計考量,進而做出更符合團隊期待的決策。 另外,即使 Base Branch 尚未 Rebase 到最新的 main,實務上仍然可以基於該 Branch 繼續開發。當上游 PR 合併後,再透過 Rebase 或 Merge 的方式同步即可,因此不一定會阻塞後續工作。對於彼此高度相關的功能來說,Stack PR 也能更清楚地呈現變更之間的關聯性,讓開發與 Review 的脈絡更加連貫。 對於相關性功能的 pr 該怎麼選擇 base branch 我想聽聽 @rileychh 的想法 |
我認同 Stack PR 除了方便 Review 之外,確實也可以用來表達功能之間的演進關係。透過 PR Chain,後續參與開發的人可以理解目前的開發方向,以及不同變更之間的脈絡。不過我覺得關鍵不只是「這些功能是否相關」,而是「後續 PR 是否真的必須建立在前一個 PR 的具體實作之上」,以及「前一個 PR 的設計是否已經相對穩定」。 就「哪些設計已經確定、哪些部分仍在調整」這點來說,我會傾向把已經 merge 到 我覺得新功能的開發,應該盡量建立在已經有共識的基礎上。以 #147 來說,如果 #105 的設計改動很大,#147 就必須跟著 rebase,甚至重寫部分實作。對於真正緊密依賴的功能,這是合理的,例如 #167 依賴 #147,或 #161 依賴 #105;但我覺得我們也需要謹慎判斷一個功能是否真的「緊密到必須線性開發」。例如,不會因為「議程表詳細頁面」還沒有共識,「手機的新檢視 UI」就不能上線;同理,也不會因為手機版還沒完成,就影響議程表篩選或收藏功能的發佈。 如果目的是讓開發者掌握彼此的實作方式和設計考量,我會更傾向在 Review 過程中明確提出設計建議。例如你在 #172 的 comment 裡提到「SessionsTable 不要有篩選功能」,我覺得就是很有幫助的設計回饋。這類建議可以直接影響後續實作方向,也能讓相關 PR 在 main 之上各自調整。但我不太覺得這個問題適合用 Stack PR 來解決,因為 Stack PR 反而可能讓其他人依賴到仍在實驗、隨時可能重寫的設計,導致 stack 越後面的 PR 越需要跟著變動。
至於「Base Branch 尚未 rebase 到最新 main,實務上仍然可以繼續基於該 branch 開發」這點,我理解理論上是可行的,但在我們目前的協作模式下,我對它的實務效率比較保留。舉例來說,我當時是等到 #102 合併到已包含 #159 的 main 之後,才開始動工議程表相關程式碼。原因是雖然沒有完全被阻塞,但我希望能用 Codex 開發;如果我直接 stack 到 #102,為了讓開發環境接近最新 main,勢必需要建立一個把 main merge 進來的 merge commit。這樣一來,PR 的 Changes tab 會同時包含 main 的變動和我真正要提交的功能變動,反而會增加 Review 成本。 當然,如果 #102 已經 rebase 到最新 main,而新的議程表細項功能又確實高度依賴 #102,那把新 PR 開在 #102 上會是合理的 Stack PR 用法。但就像我前面提到的,COSCUP 通常是非同步開發;即使 PR 已經全員 approved,也可能要等一段時間才會 rebase 或 merge。在這種協作節奏下,過度使用 Stack PR 可能不一定會提升效率,反而會讓後續 PR 更容易受到上游未合併 PR 的狀態影響。
#102 在 Pan 請求合併後,一週後執行 rebase main 和 merge 所以我的看法不是完全避免 Stack PR,而是應該把它限制在「真的有強依賴,且上游設計已經相對穩定」的情境。對於可以獨立 review、獨立 merge、獨立發佈的功能,我會傾向讓它們以 main 為共同基礎開發,而不是全部串成一條線性的 PR Chain。就這幾個功能來說,我認同它們都屬於議程表相關的開發方向,但我不認為它們彼此緊密到必須採取線性開發。 |
|
Stack 工作流對我來說是在進度超前、大量修改的時候,將大量 diff 拆分成可閱讀的 PR 工具。雖然他可以自然的表達專案的演進,不過 |
|
感謝 @mirumodapon 合併了他的 base PR,我這裡解決一下衝突!❤️ |
bac9d11 to
1950e81
Compare
|
docs/ 的圖片記得要移掉 |
mirumodapon
left a comment
There was a problem hiding this comment.
unocss 都有定義顏色,使用顏色變數來取代直接寫死的色號
|
我忘記改 Draft 了,我在修 AI slop |
1950e81 to
342484a
Compare
|
非常感謝 @mirumodapon 的 Review,也非常不好意思由於 PR 狀態設定上的失誤,讓你 review 到尚未完成的程式碼,不好意思讓你傷眼了。我已經把程式碼清理完成了,你可以再測試看看各種情況,或者看看 code quality 是否符合標準。另外我也為 #176 預備好了 |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 342484aa7d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Rebuild the favorites (#160) and share (#95) features on the session-filter architecture from #172: page-level state computed into the displayed list, shared base components, and feature components that compose them. - CpSessionItem gains favorite/readonly bookmark state (orange filled icon). - CpSessionTable/CpSessionList forward favorites + a preview (read-only) mode. - session.vue layers the all/favorite/shared view on top of filteredSessions from useSessionFilter, keeping the filter bar and empty banner. - CpSessionViewToggle and CpSessionShareButton compose the shared CpButton. - CpSessionEmptyBanner gains filter/favorite/shared variants. - useFavorites store (provide/inject + useStorage) and cp-orange palette. Closes #160 Closes #95
Rebuild the favorites (#160) and share (#95) features on the session-filter architecture from #172: page-level state computed into the displayed list, shared base components, and feature components that compose them. - CpSessionItem gains favorite/readonly bookmark state (orange filled icon). - CpSessionTable/CpSessionList forward favorites + a preview (read-only) mode. - session.vue layers the all/favorite/shared view on top of filteredSessions from useSessionFilter, keeping the filter bar and empty banner. - CpSessionViewToggle and CpSessionShareButton compose the shared CpButton. - CpSessionEmptyBanner gains filter/favorite/shared variants. - useFavorites store (provide/inject + useStorage) and cp-orange palette. Closes #160 Closes #95
Rebuild the favorites (#160) and share (#95) features on the session-filter architecture from #172: page-level state computed into the displayed list, shared base components, and feature components that compose them. - CpSessionItem gains favorite/readonly bookmark state (orange filled icon). - CpSessionTable/CpSessionList forward favorites + a preview (read-only) mode. - session.vue layers the all/favorite/shared view on top of filteredSessions from useSessionFilter, keeping the filter bar and empty banner. - CpSessionViewToggle and CpSessionShareButton compose the shared CpButton. - CpSessionEmptyBanner gains filter/favorite/shared variants. - useFavorites store (provide/inject + useStorage) and cp-orange palette. Closes #160 Closes #95
Rebuild the favorites (#160) and share (#95) features on the session-filter architecture from #172: page-level state computed into the displayed list, shared base components, and feature components that compose them. - CpSessionItem gains favorite/readonly bookmark state (orange filled icon). - CpSessionTable/CpSessionList forward favorites + a preview (read-only) mode. - session.vue layers the all/favorite/shared view on top of filteredSessions from useSessionFilter, keeping the filter bar and empty banner. - CpSessionViewToggle and CpSessionShareButton compose the shared CpButton. - CpSessionEmptyBanner gains filter/favorite/shared variants. - useFavorites store (provide/inject + useStorage) and cp-orange palette. Closes #160 Closes #95
|
嗨!我想在這個 pr 加個功能,希望這些篩選或是搜尋的功能,可以同步網址,並且使得這個搜尋結果可以透過網址傳遞,可以參考 2024 的功能行為:https://coscup.org/2024/zh-TW/session?room=TR411&tags=Elementary&type=02bf7j |
500fbb1 to
84de352
Compare


Resolves #94:在議程頁(
/2026/session、/2026/en/session)加入頁面層級的搜尋與篩選,並補上對應的空狀態。變更內容(Changelog)
新增功能
CpSessionEmptyBanner(「找不到符合條件的議程。」)。noSession(「尚未公布,敬請期待。」)。clampToOptions)。元件與架構
useSessionFilter.ts:集中管理搜尋字串、教室/標籤選取、選項產生(去重 + 依語系排序)與filteredSessions計算;全程使用 computed,不使用watch。CpSessionFilterBar、CpSessionFilterDropdown、CpSessionSearchField、CpSessionEmptyBanner。CpButton、CpTextField、CpCheckbox。app/pages/session.vue持有搜尋/篩選狀態,將filteredSessions傳入桌機表格CpSessionTable與手機清單CpSessionList,表格/清單只負責渲染。響應式 / 互動
sm(640px):桌機顯示表格、手機顯示清單。sticky釘頂部、手機fixed釘底部(含 safe-area)。sticky left-0,水平拖曳表格時維持貼齊視窗左側。useDragScroll,scrollTarget = window):拖曳超過 4px 後抑制點擊,避免誤觸議程卡片導頁。測試計畫(Test Plan)
1. 前置
zh)與英文(en)各跑一輪,確認 i18n 字串正確。/session無 console error。2. DaySelector(換日)
bg-primary-400、其餘bg-gray-200;點其他天內容即時更新。28.Jul形式)。3. 教室 / 標籤篩選
zh-Hant、英文en)。4. 搜尋
sm:w-80)、手機滿版。5. 組合篩選與換日重置
6. 空狀態 / 無此議程
CpSessionEmptyBanner,表格/清單皆不顯示,DaySelector / FilterBar 仍在。完全沒公布議程(無任何天)→ 顯示「尚未公布,敬請期待。」,不渲染 DaySelector/Filter。(已經沒有這種狀態)7. 拖曳 / 捲動時的釘選定位
--viewport-width有更新、釘選寬度正確。z-50)> 下拉(z-30)> DaySelector(z-10),展開的下拉不被遮擋。8. 響應式切換