diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml index 75ed96f..3b10d51 100644 --- a/.github/workflows/deploy-pages.yml +++ b/.github/workflows/deploy-pages.yml @@ -3,6 +3,8 @@ name: Deploy GitHub Pages on: push: branches: ["main"] + schedule: + - cron: "17 2 * * *" workflow_dispatch: permissions: @@ -45,6 +47,10 @@ jobs: - name: Sync Content run: npm run content:sync + - name: Check official conference sources + if: github.event_name != 'push' + run: npm run deadlines:refresh + - name: Validate Content run: npm run validate:content diff --git a/content/deadlines/venues.json b/content/deadlines/venues.json new file mode 100644 index 0000000..7872151 --- /dev/null +++ b/content/deadlines/venues.json @@ -0,0 +1,751 @@ +{ + "meta": { + "title": "CVL-Lab conference deadlines", + "updated_at": "2026-08-12T09:00:00+09:00", + "display_timezone": "Asia/Seoul", + "area_order": [ + "computer-vision", + "machine-learning", + "llm-nlp", + "robotics" + ], + "area_labels": { + "computer-vision": "Computer Vision", + "machine-learning": "Machine Learning / AI", + "llm-nlp": "LLM / NLP", + "robotics": "Robotics" + } + }, + "venues": [ + { + "id": "cvpr", + "name": "CVPR", + "full_name": "IEEE/CVF Conference on Computer Vision and Pattern Recognition", + "areas": ["computer-vision"], + "official_url": "https://cvpr.thecvf.com/", + "cfp_url": "https://cvpr.thecvf.com/", + "status": "awaiting_cfp", + "event": { + "dates": "June 20-24, 2027", + "location": "Seattle, Washington, USA", + "venue": "Venue TBA", + "source_url": "https://www.thecvf.com/?p=137", + "status": "official" + }, + "milestones": [] + }, + { + "id": "iccv", + "name": "ICCV", + "full_name": "IEEE/CVF International Conference on Computer Vision", + "areas": ["computer-vision"], + "official_url": "https://iccv.thecvf.com/", + "cfp_url": "https://iccv.thecvf.com/", + "status": "awaiting_cfp", + "event": { + "dates": "October 2-8, 2027", + "location": "Hong Kong", + "venue": "Venue TBA", + "source_url": "https://www.thecvf.com/?p=137", + "status": "official" + }, + "milestones": [] + }, + { + "id": "eccv", + "name": "ECCV", + "full_name": "European Conference on Computer Vision", + "areas": ["computer-vision"], + "official_url": "https://eccv.ecva.net/", + "cfp_url": "https://eccv.ecva.net/", + "status": "awaiting_cfp", + "event": { + "dates": "September 8-12, 2026", + "location": "Malmo, Sweden", + "venue": "Malmo Arena and Malmomassan", + "source_url": "https://eccv.ecva.net/Conferences/2026", + "status": "official" + }, + "milestones": [] + }, + { + "id": "wacv", + "name": "WACV 2027", + "full_name": "IEEE/CVF Winter Conference on Applications of Computer Vision", + "areas": ["computer-vision"], + "official_url": "https://wacv.thecvf.com/", + "cfp_url": "https://wacv.thecvf.com/", + "status": "verified", + "source_checked_at": "2026-08-12T09:00:00+09:00", + "source_evidence": "Round 2 Paper Submissions", + "event": { + "dates": "January 4-8, 2027", + "location": "Buena Vista, Florida, USA", + "venue": "Disney Springs", + "source_url": "https://wacv.thecvf.com/", + "status": "official" + }, + "milestones": [ + { + "id": "round-2-registration", + "label": "Round 2 registration", + "short_label": "Registration", + "kind": "registration", + "deadline_at": "2026-08-21T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 New Paper Registration.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + }, + { + "id": "round-2-paper", + "label": "Round 2 paper submission", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-08-28T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 Paper Submissions.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + }, + { + "id": "round-2-supplementary", + "label": "Round 2 supplementary material", + "short_label": "Supplementary", + "kind": "supplementary", + "deadline_at": "2026-08-30T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 Supplemental Material.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + }, + { + "id": "final-decision", + "label": "Final decisions released", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2026-10-09T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 Reviews and Final Decisions to Authors.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + } + ] + }, + { + "id": "bmvc", + "name": "BMVC", + "full_name": "British Machine Vision Conference", + "areas": ["computer-vision"], + "official_url": "https://bmvc2026.org/", + "cfp_url": "https://bmvc2026.org/", + "status": "awaiting_cfp", + "event": { + "dates": "November 23-26, 2026", + "location": "Lancaster, United Kingdom", + "venue": "Venue TBA", + "source_url": "https://www.bmva.org/bmvc", + "status": "official" + }, + "milestones": [] + }, + { + "id": "accv", + "name": "ACCV", + "full_name": "Asian Conference on Computer Vision", + "areas": ["computer-vision"], + "official_url": "https://accv2026.org/", + "cfp_url": "https://accv2026.org/", + "status": "awaiting_cfp", + "event": { + "dates": "December 14-18, 2026", + "location": "Osaka, Japan", + "venue": "Venue TBA", + "source_url": "https://accv2026.org/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "3dv", + "name": "3DV", + "full_name": "International Conference on 3D Vision", + "areas": ["computer-vision"], + "official_url": "https://3dvconf.github.io/2027/", + "cfp_url": "https://3dvconf.github.io/2027/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Paper Submission", + "event": { + "dates": "April 6-9, 2027", + "location": "Thessaloniki, Greece", + "venue": "Thessaloniki Concert Hall (M2 building)", + "source_url": "https://3dvconf.github.io/2027/", + "status": "official" + }, + "milestones": [ + { + "id": "paper", + "label": "Paper submission", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-08-28T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Paper Submission\\s+([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "supplementary", + "label": "Supplementary material", + "short_label": "Supplementary", + "kind": "supplementary", + "deadline_at": "2026-09-02T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Supplementary Material\\s+([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "preliminary-notification", + "label": "Preliminary notification", + "short_label": "Prelim. decision", + "kind": "notification", + "deadline_at": "2026-10-27T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + }, + { + "id": "final-notification", + "label": "Final notification", + "short_label": "Final decision", + "kind": "notification", + "deadline_at": "2026-12-02T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ] + }, + { + "id": "miccai", + "name": "MICCAI", + "full_name": "Medical Image Computing and Computer Assisted Intervention", + "areas": ["computer-vision"], + "official_url": "https://conferences.miccai.org/", + "cfp_url": "https://conferences.miccai.org/", + "status": "awaiting_cfp", + "event": { + "dates": "September 27-October 1, 2026", + "location": "Strasbourg, France", + "venue": "Strasbourg Convention Center", + "source_url": "https://conferences.miccai.org/2026/en/default.asp", + "status": "official" + }, + "milestones": [] + }, + { + "id": "neurips", + "name": "NeurIPS", + "full_name": "Conference on Neural Information Processing Systems", + "areas": ["machine-learning", "llm-nlp"], + "official_url": "https://neurips.cc/", + "cfp_url": "https://neurips.cc/", + "status": "awaiting_cfp", + "event": { + "dates": "December 6-13, 2026", + "location": "Sydney, Australia; Atlanta, Georgia, USA; Paris, France", + "venue": "Multi-site conference", + "source_url": "https://neurips.cc/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "icml", + "name": "ICML", + "full_name": "International Conference on Machine Learning", + "areas": ["machine-learning"], + "official_url": "https://icml.cc/", + "cfp_url": "https://icml.cc/", + "status": "awaiting_cfp", + "event": { + "dates": "July 6-11, 2026", + "location": "Seoul, South Korea", + "venue": "COEX Convention & Exhibition Center", + "source_url": "https://icml.cc/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "iclr", + "name": "ICLR 2027", + "full_name": "International Conference on Learning Representations", + "areas": ["machine-learning", "llm-nlp", "robotics"], + "official_url": "https://iclr.cc/", + "cfp_url": "https://iclr.cc/Conferences/2027/AuthorGuidelines", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "full paper submission deadline", + "event": { + "dates": "October 6-9, 2026", + "location": "California, USA", + "venue": "Venue TBA", + "source_url": "https://iclr.cc/Conferences/2027", + "status": "official" + }, + "milestones": [ + { + "id": "abstract", + "label": "Abstract deadline", + "short_label": "Abstract", + "kind": "abstract", + "deadline_at": "2026-09-18T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "abstract submission deadline(?:\\s+of)?\\s+([A-Za-z]+\\s+\\d{1,2}(?:,)?\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "paper", + "label": "Paper deadline", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-09-25T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "full paper submission deadline(?:\\s+is)?\\s+([A-Za-z]+\\s+\\d{1,2}(?:,)?\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "reviews", + "label": "Reviews released", + "short_label": "Reviews", + "kind": "review", + "deadline_at": "2026-11-05T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + }, + { + "id": "decision", + "label": "Final decisions", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2026-12-16T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ] + }, + { + "id": "aaai", + "name": "AAAI", + "full_name": "AAAI Conference on Artificial Intelligence", + "areas": ["machine-learning", "llm-nlp", "robotics"], + "official_url": "https://aaai.org/conference/aaai/aaai-27/", + "cfp_url": "https://aaai.org/conference/aaai/aaai-27/main-technical-track-call/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Full papers due", + "event": { + "dates": "February 16-23, 2027", + "location": "Montreal, Canada", + "venue": "Venue TBA", + "source_url": "https://aaai.org/conference/aaai/aaai-27/", + "status": "official" + }, + "milestones": [ + { + "id": "abstract", + "label": "Abstract deadline", + "short_label": "Abstract", + "kind": "abstract", + "deadline_at": "2026-07-21T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})\\s+Abstracts due", + "timezone": "AoE" + } + }, + { + "id": "paper", + "label": "Full paper deadline", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-07-28T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})\\s+Full papers due", + "timezone": "AoE" + } + }, + { + "id": "supplementary", + "label": "Supplementary material and code", + "short_label": "Supplementary", + "kind": "supplementary", + "deadline_at": "2026-07-31T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})\\s+Supplementary material and code due", + "timezone": "AoE" + } + }, + { + "id": "phase-1-decision", + "label": "Phase 1 rejection notification", + "short_label": "Phase 1 decision", + "kind": "notification", + "deadline_at": "2026-09-24T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + }, + { + "id": "final-decision", + "label": "Final acceptance notification", + "short_label": "Final decision", + "kind": "notification", + "deadline_at": "2026-11-30T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ] + }, + { + "id": "aistats", + "name": "AISTATS", + "full_name": "International Conference on Artificial Intelligence and Statistics", + "areas": ["machine-learning"], + "official_url": "https://aistats.org/", + "cfp_url": "https://aistats.org/", + "status": "awaiting_cfp", + "event": { + "dates": "May 2-5, 2026", + "location": "Tangier, Morocco", + "venue": "Hilton Tangier Al Houara Resort & Spa", + "source_url": "https://virtual.aistats.org/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "uai", + "name": "UAI", + "full_name": "Conference on Uncertainty in Artificial Intelligence", + "areas": ["machine-learning"], + "official_url": "https://www.auai.org/uai/", + "cfp_url": "https://www.auai.org/uai/", + "status": "awaiting_cfp", + "event": { + "dates": "August 17-21, 2026", + "location": "Amsterdam, Netherlands", + "venue": "KIT Events", + "source_url": "https://www.auai.org/uai2026/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "acl", + "name": "ACL", + "full_name": "Annual Meeting of the Association for Computational Linguistics", + "areas": ["llm-nlp"], + "official_url": "https://www.aclweb.org/portal/", + "cfp_url": "https://www.aclweb.org/portal/", + "status": "awaiting_cfp", + "event": { + "dates": "August 17-22, 2027", + "location": "Kyoto, Japan", + "venue": "Venue TBA", + "source_url": "https://2027.aclweb.org/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "emnlp", + "name": "EMNLP", + "full_name": "Conference on Empirical Methods in Natural Language Processing", + "areas": ["llm-nlp"], + "official_url": "https://2026.emnlp.org/", + "cfp_url": "https://2026.emnlp.org/", + "status": "awaiting_cfp", + "event": { + "dates": "October 22-29, 2026", + "location": "Budapest, Hungary", + "venue": "Hungexpo", + "source_url": "https://2026.emnlp.org/venue/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "naacl", + "name": "NAACL", + "full_name": "North American Chapter of the Association for Computational Linguistics", + "areas": ["llm-nlp"], + "official_url": "https://2027.naacl.org/", + "cfp_url": "https://2027.naacl.org/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Submission deadline (ARR)", + "event": { + "dates": "June 1-5, 2027", + "location": "San Francisco, California, USA", + "venue": "Venue TBA", + "source_url": "https://2027.naacl.org/", + "status": "official" + }, + "milestones": [ + { + "id": "arr-submission", + "label": "ARR submission deadline", + "short_label": "ARR submission", + "kind": "paper", + "deadline_at": "2026-10-12T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Submission deadline \\(ARR\\)[^A-Za-z]{0,20}([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + } + ] + }, + { + "id": "eacl", + "name": "EACL", + "full_name": "European Chapter of the Association for Computational Linguistics", + "areas": ["llm-nlp"], + "official_url": "https://2027.eacl.org/", + "cfp_url": "https://2027.eacl.org/calls/papers/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "ARR submission deadline", + "event": { + "dates": "March 9-14, 2027", + "location": "Athens, Greece", + "venue": "Venue TBA", + "source_url": "https://2027.eacl.org/", + "status": "official" + }, + "milestones": [ + { + "id": "arr-submission", + "label": "ARR submission deadline", + "short_label": "ARR submission", + "kind": "paper", + "deadline_at": "2026-08-03T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "ARR submission deadline \\(long (?:&|&) short papers\\)[^A-Za-z]{0,20}([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "commitment", + "label": "EACL commitment deadline", + "short_label": "Commitment", + "kind": "paper", + "deadline_at": "2026-10-11T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "EACL 2027 commitment deadline[^A-Za-z]{0,20}([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "notification", + "label": "Acceptance notification", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2026-11-12T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ] + }, + { + "id": "aacl", + "name": "AACL", + "full_name": "Asia-Pacific Chapter of the Association for Computational Linguistics", + "areas": ["llm-nlp"], + "official_url": "https://www.aaclweb.org/", + "cfp_url": "https://www.aaclweb.org/", + "status": "awaiting_cfp", + "event": { + "dates": "November 6-10, 2026", + "location": "Hengqin, China", + "venue": "Venue TBA", + "source_url": "https://2026.aaclnet.org/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "coling", + "name": "COLING", + "full_name": "International Conference on Computational Linguistics", + "areas": ["llm-nlp"], + "official_url": "https://coling2025.org/", + "cfp_url": "https://coling2025.org/", + "status": "awaiting_cfp", + "event": { + "dates": "May 9-14, 2027", + "location": "Macau, China", + "venue": "Venue TBA", + "source_url": "https://2027.coling-iccl.org/about/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "conll", + "name": "CoNLL", + "full_name": "Conference on Computational Natural Language Learning", + "areas": ["llm-nlp"], + "official_url": "https://www.conll.org/", + "cfp_url": "https://www.conll.org/", + "status": "awaiting_cfp", + "event": { + "dates": "July 3-4, 2026", + "location": "San Diego, California, USA", + "venue": "Co-located with ACL 2026", + "source_url": "https://www.conll.org/2026", + "status": "official" + }, + "milestones": [] + }, + { + "id": "icra", + "name": "ICRA", + "full_name": "IEEE International Conference on Robotics and Automation", + "areas": ["robotics"], + "official_url": "https://2027.ieee-icra.org/", + "cfp_url": "https://2027.ieee-icra.org/announcements/call-for-technical-papers/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Contributed paper submission deadline", + "event": { + "dates": "May 24-28, 2027", + "location": "Seoul, South Korea", + "venue": "COEX", + "source_url": "https://2027.ieee-icra.org/", + "status": "official" + }, + "milestones": [ + { + "id": "paper", + "label": "Contributed paper submission", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-09-15T23:59:00-07:00", + "timezone_label": "Pacific Time (PT)", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})[\\s\\S]{0,80}11:59[\\s\\S]{0,80}Contributed paper submission deadline", + "timezone": "America/Los_Angeles" + } + }, + { + "id": "decision", + "label": "Notification of decision", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2027-01-31T23:59:00-08:00", + "timezone_label": "Pacific Time (PT)", + "status": "verified" + } + ] + }, + { + "id": "iros", + "name": "IROS", + "full_name": "IEEE/RSJ International Conference on Intelligent Robots and Systems", + "areas": ["robotics"], + "official_url": "https://www.ieee-ras.org/event/2027-ieee-rsj-international-conference-on-intelligent-robots-and-systems-iros-70525/", + "cfp_url": "https://www.ieee-ras.org/event/2027-ieee-rsj-international-conference-on-intelligent-robots-and-systems-iros-70525/", + "status": "awaiting_cfp", + "event": { + "dates": "September 26-October 1, 2027", + "location": "Florence, Italy", + "venue": "Fortezza da Basso", + "source_url": "https://www.ieee-ras.org/event/2027-ieee-rsj-international-conference-on-intelligent-robots-and-systems-iros-70525/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "rss", + "name": "RSS", + "full_name": "Robotics: Science and Systems", + "areas": ["robotics"], + "official_url": "https://roboticsconference.org/", + "cfp_url": "https://roboticsconference.org/", + "status": "awaiting_cfp", + "event": { + "dates": "July 13-17, 2026", + "location": "Sydney, Australia", + "venue": "University of Technology Sydney and ICC Sydney", + "source_url": "https://roboticsconference.org/", + "status": "official" + }, + "milestones": [] + }, + { + "id": "corl", + "name": "CoRL", + "full_name": "Conference on Robot Learning", + "areas": ["robotics", "machine-learning"], + "official_url": "https://www.corl.org/", + "cfp_url": "https://www.corl.org/", + "status": "awaiting_cfp", + "event": { + "dates": "November 9-12, 2026", + "location": "Austin, Texas, USA", + "venue": "JW Marriott Austin, Hilton Austin, and Bass Hall", + "source_url": "https://www.corl.org/home", + "status": "official" + }, + "milestones": [] + }, + { + "id": "humanoids", + "name": "Humanoids", + "full_name": "IEEE-RAS International Conference on Humanoid Robots", + "areas": ["robotics"], + "official_url": "https://www.ieee-ras.org/conferences-workshops/financially-co-sponsored/humanoids", + "cfp_url": "https://www.ieee-ras.org/conferences-workshops/financially-co-sponsored/humanoids", + "status": "awaiting_cfp", + "event": { + "dates": "December 7-9, 2026", + "location": "Santa Clara, California, USA", + "venue": "Santa Clara Convention Center", + "source_url": "https://www.ieee-ras.org/event/2026-ieee-ras-25th-international-conference-on-humanoid-robots-humanoids-68892/", + "status": "official" + }, + "milestones": [] + } + ] +} diff --git a/docs/deadlines/README.md b/docs/deadlines/README.md new file mode 100644 index 0000000..a399b4b --- /dev/null +++ b/docs/deadlines/README.md @@ -0,0 +1,53 @@ +# Conference deadlines + +`/deadlines` is a venue-centered schedule for the lab's core Computer Vision, Machine Learning / AI, LLM / NLP, and Robotics venues. + +## Data source + +Edit `content/deadlines/venues.json`. Every venue has a direct official CFP URL, event details, a status, and zero or more milestones. A missing milestone is intentional: it means the next CFP has not published that date yet. + +Use the `event` object to show the conference period, city/country, and the specific convention venue. Use `Venue TBA` when the organiser has announced the host city but not the building. Keep `source_url` pointed to the official event page. + +```json +{ + "event": { + "dates": "June 20–24, 2027", + "location": "Seattle, WA, USA", + "venue": "Venue TBA", + "source_url": "https://example.org/official-event", + "status": "official" + } +} +``` + +Each milestone stores the official instant with its timezone. Use a full ISO 8601 value with an offset; for Anywhere on Earth, use `-12:00`. + +```json +{ + "id": "paper", + "label": "Paper deadline", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-09-25T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" +} +``` + +Do not add a date by extrapolating from a previous year. Use `awaiting_cfp` until a current official CFP publishes it, then set the venue to `verified`, update `source_checked_at`, and add the official date. + +## Commands + +```bash +npm run deadlines:validate +npm run deadlines:sync +npm run deadlines:refresh +``` + +`deadlines:sync` validates the source file and creates `src/generated/deadlines.generated.json` for the site. `deadlines:refresh` additionally checks every official CFP URL, records a source-health result in that generated file, confirms configured evidence text for verified venues, and applies a configured venue-specific date extractor when it can prove a current official date. + +The deployment workflow runs the source check once each day at 02:17 UTC. The published site calculates the countdown in the visitor's browser, so the remaining time updates every second without waiting for another deployment. + +## Adding a source parser + +Official CFP formats differ widely. The initial automation parses the stable WACV and ICLR formats and monitors the remaining official sources. When another venue's current CFP format is stable, add a `source_parser` to its milestone, validate its output against the source, and retain the previous verified date if the parser cannot prove a replacement value. This avoids silently publishing an incorrect deadline after a conference site redesign. diff --git a/package.json b/package.json index 574beac..041532d 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "photos:sync": "node scripts/content/photos.mjs", "people:sync": "node scripts/content/people.mjs", "people:validate": "node scripts/content/people.mjs --validate-only", + "deadlines:sync": "node scripts/content/deadlines.mjs", + "deadlines:refresh": "node scripts/content/deadlines.mjs --refresh-sources", + "deadlines:validate": "node scripts/content/deadlines.mjs --validate-only", "research:validate": "node scripts/content/research.mjs", "validate:content": "node scripts/content/validate.mjs", "audit:dependencies": "better-npm-audit audit --level low --exclude GHSA-qwww-vcr4-c8h2", diff --git a/scripts/content/deadlines.mjs b/scripts/content/deadlines.mjs new file mode 100644 index 0000000..eee8d0b --- /dev/null +++ b/scripts/content/deadlines.mjs @@ -0,0 +1,413 @@ +import { createHash } from "node:crypto"; +import path from "node:path"; +import { promises as fs } from "node:fs"; +import { GENERATED_DIR, ROOT_DIR, writeJsonFile } from "./lib.mjs"; + +const DEADLINE_CONTENT_FILE = path.resolve( + ROOT_DIR, + "content/deadlines/venues.json", +); +const DEADLINE_GENERATED_FILE = path.resolve( + GENERATED_DIR, + "deadlines.generated.json", +); +const SOURCE_TIMEOUT_MS = 15_000; +const MONTH_INDEX = { + jan: "01", + january: "01", + feb: "02", + february: "02", + mar: "03", + march: "03", + apr: "04", + april: "04", + may: "05", + jun: "06", + june: "06", + jul: "07", + july: "07", + aug: "08", + august: "08", + sep: "09", + sept: "09", + september: "09", + oct: "10", + october: "10", + nov: "11", + november: "11", + dec: "12", + december: "12", +}; + +const isNonEmptyString = (value) => + typeof value === "string" && value.trim().length > 0; + +const isHttpUrl = (value) => { + if (!isNonEmptyString(value)) { + return false; + } + + try { + const url = new URL(value); + return url.protocol === "https:" || url.protocol === "http:"; + } catch { + return false; + } +}; + +const validateDeadline = (deadline, venueId, index) => { + const prefix = `[deadlines] venues.${venueId}.milestones[${index}]`; + + for (const field of ["id", "label", "short_label", "kind", "timezone_label", "status"]) { + if (!isNonEmptyString(deadline?.[field])) { + throw new Error(`${prefix}.${field} must be a non-empty string.`); + } + } + + if (!isNonEmptyString(deadline.deadline_at)) { + throw new Error(`${prefix}.deadline_at must be a non-empty ISO date.`); + } + + if (Number.isNaN(Date.parse(deadline.deadline_at))) { + throw new Error(`${prefix}.deadline_at is not a valid date.`); + } + + if (deadline.source_parser !== undefined) { + if ( + !deadline.source_parser || + !isNonEmptyString(deadline.source_parser.match) || + !isNonEmptyString(deadline.source_parser.timezone) + ) { + throw new Error( + `${prefix}.source_parser must contain match and timezone strings.`, + ); + } + + try { + new RegExp(deadline.source_parser.match, "i"); + } catch { + throw new Error(`${prefix}.source_parser.match is not a valid regular expression.`); + } + } +}; + +const validateEvent = (event, prefix) => { + if (event === undefined) { + return; + } + + if (!event || typeof event !== "object" || Array.isArray(event)) { + throw new Error(`${prefix}.event must be an object when provided.`); + } + + for (const field of ["dates", "location", "venue", "source_url", "status"]) { + if (!isNonEmptyString(event[field])) { + throw new Error(`${prefix}.event.${field} must be a non-empty string.`); + } + } + + if (!isHttpUrl(event.source_url)) { + throw new Error(`${prefix}.event.source_url must be a valid URL.`); + } +}; + +export const validateDeadlineContent = (data) => { + if (!data || typeof data !== "object" || Array.isArray(data)) { + throw new Error("[deadlines] content must be a JSON object."); + } + + if (!Array.isArray(data.meta?.area_order) || !data.meta.area_order.length) { + throw new Error("[deadlines] meta.area_order must be a non-empty array."); + } + + if (!data.meta?.area_labels || typeof data.meta.area_labels !== "object") { + throw new Error("[deadlines] meta.area_labels must be an object."); + } + + const areaSet = new Set(data.meta.area_order); + if (areaSet.size !== data.meta.area_order.length) { + throw new Error("[deadlines] meta.area_order contains duplicates."); + } + data.meta.area_order.forEach((area) => { + if (!isNonEmptyString(data.meta.area_labels[area])) { + throw new Error(`[deadlines] meta.area_labels.${area} is missing.`); + } + }); + + if (!Array.isArray(data.venues) || !data.venues.length) { + throw new Error("[deadlines] venues must be a non-empty array."); + } + + const venueIds = new Set(); + data.venues.forEach((venue, index) => { + const prefix = `[deadlines] venues[${index}]`; + for (const field of ["id", "name", "full_name", "official_url", "cfp_url", "status"]) { + if (!isNonEmptyString(venue?.[field])) { + throw new Error(`${prefix}.${field} must be a non-empty string.`); + } + } + + if (venueIds.has(venue.id)) { + throw new Error(`${prefix}.id duplicates ${venue.id}.`); + } + venueIds.add(venue.id); + + if (!isHttpUrl(venue.official_url) || !isHttpUrl(venue.cfp_url)) { + throw new Error(`${prefix} must use valid official and CFP URLs.`); + } + + validateEvent(venue.event, prefix); + + if ( + !Array.isArray(venue.areas) || + !venue.areas.length || + venue.areas.some((area) => !areaSet.has(area)) + ) { + throw new Error(`${prefix}.areas must contain known area keys.`); + } + + if (!Array.isArray(venue.milestones)) { + throw new Error(`${prefix}.milestones must be an array.`); + } + + const milestoneIds = new Set(); + venue.milestones.forEach((deadline, milestoneIndex) => { + validateDeadline(deadline, venue.id, milestoneIndex); + if (milestoneIds.has(deadline.id)) { + throw new Error( + `[deadlines] venues.${venue.id} contains duplicate milestone ${deadline.id}.`, + ); + } + milestoneIds.add(deadline.id); + }); + + if (venue.status === "verified" && !isNonEmptyString(venue.source_checked_at)) { + throw new Error(`${prefix}.source_checked_at is required for verified data.`); + } + }); +}; + +const readDeadlineContent = async () => { + let raw; + try { + raw = await fs.readFile(DEADLINE_CONTENT_FILE, "utf8"); + } catch (error) { + throw new Error( + `[deadlines] Cannot read ${path.relative(ROOT_DIR, DEADLINE_CONTENT_FILE)}: ${error.message || error}`, + ); + } + + try { + return JSON.parse(raw); + } catch (error) { + throw new Error( + `[deadlines] Invalid JSON in ${path.relative(ROOT_DIR, DEADLINE_CONTENT_FILE)}: ${error.message || error}`, + ); + } +}; + +const normalizePageText = (html) => + html + .replace(//gi, " ") + .replace(//gi, " ") + .replace(/<[^>]+>/g, " ") + .replace(/ /gi, " ") + .replace(/\s+/g, " ") + .trim(); + +const getTimezoneOffset = (date, timezone) => { + if (timezone === "AoE") { + return "-12:00"; + } + + if (timezone !== "America/Los_Angeles") { + return null; + } + + const parts = new Intl.DateTimeFormat("en-US", { + timeZone: timezone, + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + hourCycle: "h23", + }) + .formatToParts(date) + .reduce((result, part) => ({ ...result, [part.type]: part.value }), {}); + const localAsUtc = Date.UTC( + Number(parts.year), + Number(parts.month) - 1, + Number(parts.day), + Number(parts.hour), + Number(parts.minute), + ); + const offsetMinutes = Math.round((localAsUtc - date.getTime()) / 60_000); + const sign = offsetMinutes >= 0 ? "+" : "-"; + const absoluteMinutes = Math.abs(offsetMinutes); + const hours = String(Math.floor(absoluteMinutes / 60)).padStart(2, "0"); + const minutes = String(absoluteMinutes % 60).padStart(2, "0"); + return `${sign}${hours}:${minutes}`; +}; + +const toDeadlineAt = (dateText, timezone) => { + const match = dateText + .trim() + .match(/^([A-Za-z]+)\.?\s+(\d{1,2})(?:,)?\s+'?(\d{2}|\d{4})$/); + if (!match) { + return null; + } + + const month = MONTH_INDEX[match[1].toLowerCase()]; + const day = String(Number(match[2])).padStart(2, "0"); + const year = match[3].length === 2 ? `20${match[3]}` : match[3]; + if (!month || Number(day) < 1 || Number(day) > 31) { + return null; + } + + const date = new Date(`${year}-${month}-${day}T23:59:00Z`); + const timezoneOffset = getTimezoneOffset(date, timezone); + return timezoneOffset + ? `${year}-${month}-${day}T23:59:00${timezoneOffset}` + : null; +}; + +const extractMilestones = (venue, text) => { + const extracted = {}; + const errors = []; + + venue.milestones.forEach((milestone) => { + if (!milestone.source_parser) { + return; + } + + const matched = new RegExp(milestone.source_parser.match, "i").exec(text); + const dateText = matched?.[1] ?? ""; + const deadlineAt = toDeadlineAt(dateText, milestone.source_parser.timezone); + + if (!deadlineAt) { + errors.push(`${milestone.short_label} could not be extracted`); + return; + } + + extracted[milestone.id] = deadlineAt; + }); + + return { extracted, errors }; +}; + +const checkOfficialSource = async (venue) => { + const checkedAt = new Date().toISOString(); + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), SOURCE_TIMEOUT_MS); + + try { + const response = await fetch(venue.cfp_url, { + headers: { + "user-agent": "CVL-Lab-Deadline-Monitor/1.0 (+https://cvl-lab.github.io)", + accept: "text/html,application/xhtml+xml", + }, + redirect: "follow", + signal: controller.signal, + }); + const html = await response.text(); + const text = normalizePageText(html); + const evidence = venue.source_evidence?.trim(); + const evidenceFound = !evidence || text.toLowerCase().includes(evidence.toLowerCase()); + const { extracted, errors } = extractMilestones(venue, text); + const hasExtractionFailure = errors.length > 0; + + return { + checked_at: checkedAt, + state: + response.ok && evidenceFound && !hasExtractionFailure + ? "matched" + : "needs_review", + http_status: response.status, + content_fingerprint: createHash("sha256").update(text).digest("hex").slice(0, 16), + extracted_milestones: extracted, + message: response.ok + ? !evidenceFound + ? "Official source changed or no longer contains the configured evidence." + : hasExtractionFailure + ? `Official source needs review: ${errors.join(", ")}.` + : "Official source matched the configured evidence." + : `Official source returned HTTP ${response.status}.`, + }; + } catch (error) { + return { + checked_at: checkedAt, + state: "unavailable", + http_status: null, + content_fingerprint: null, + extracted_milestones: {}, + message: `Official source could not be checked: ${error.name === "AbortError" ? "request timed out" : error.message || String(error)}.`, + }; + } finally { + clearTimeout(timeout); + } +}; + +const buildGeneratedData = (data, sourceChecks = {}) => ({ + meta: data.meta, + venues: data.venues.map((venue) => ({ + ...venue, + milestones: venue.milestones.map((milestone) => ({ + ...milestone, + deadline_at: + sourceChecks[venue.id]?.state === "matched" && + sourceChecks[venue.id]?.extracted_milestones?.[milestone.id] + ? sourceChecks[venue.id].extracted_milestones[milestone.id] + : milestone.deadline_at, + })).sort( + (left, right) => + new Date(left.deadline_at).getTime() - + new Date(right.deadline_at).getTime(), + ), + source_check: sourceChecks[venue.id] ?? null, + })), +}); + +export const syncDeadlineContent = async ({ + validateOnly = false, + refreshSources = false, +} = {}) => { + const data = await readDeadlineContent(); + validateDeadlineContent(data); + + const sourceChecks = {}; + if (refreshSources) { + const results = await Promise.all( + data.venues.map(async (venue) => [venue.id, await checkOfficialSource(venue)]), + ); + results.forEach(([id, check]) => { + sourceChecks[id] = check; + }); + } + + const generated = buildGeneratedData(data, sourceChecks); + if (!validateOnly) { + await writeJsonFile(DEADLINE_GENERATED_FILE, generated); + } + + return generated; +}; + +const isExecutedDirectly = + process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname); + +if (isExecutedDirectly) { + const refreshSources = process.argv.includes("--refresh-sources"); + const validateOnly = process.argv.includes("--validate-only"); + + syncDeadlineContent({ validateOnly, refreshSources }) + .then((data) => { + console.log( + `[deadlines] ${validateOnly ? "validation" : refreshSources ? "source refresh" : "sync"} completed for ${data.venues.length} venues.`, + ); + }) + .catch((error) => { + console.error(error.message || error); + process.exit(1); + }); +} diff --git a/scripts/content/sync.mjs b/scripts/content/sync.mjs index 3bec0bf..dbdd2dc 100644 --- a/scripts/content/sync.mjs +++ b/scripts/content/sync.mjs @@ -3,6 +3,7 @@ import { syncPeopleImages } from "./people.mjs"; import { syncPublicationContent } from "./publications.mjs"; import { syncPhotoContent } from "./photos.mjs"; import { validateResearchContent } from "./research.mjs"; +import { syncDeadlineContent } from "./deadlines.mjs"; const run = async () => { await validateResearchContent(); @@ -10,6 +11,7 @@ const run = async () => { await syncNewsContent({ publicationItems }); await syncPhotoContent(); await syncPeopleImages(); + await syncDeadlineContent(); console.log("[content] sync completed"); }; diff --git a/scripts/content/validate.mjs b/scripts/content/validate.mjs index d2ec56e..87a096a 100644 --- a/scripts/content/validate.mjs +++ b/scripts/content/validate.mjs @@ -3,6 +3,7 @@ import { syncPeopleImages } from "./people.mjs"; import { syncPublicationContent } from "./publications.mjs"; import { syncPhotoContent } from "./photos.mjs"; import { validateResearchContent } from "./research.mjs"; +import { syncDeadlineContent } from "./deadlines.mjs"; const run = async () => { await validateResearchContent(); @@ -12,6 +13,7 @@ const run = async () => { await syncNewsContent({ validateOnly: true, publicationItems }); await syncPhotoContent({ validateOnly: true }); await syncPeopleImages({ validateOnly: true }); + await syncDeadlineContent({ validateOnly: true }); console.log("[content] validation completed"); }; diff --git a/src/App.css b/src/App.css index 31408ee..98b8ca4 100644 --- a/src/App.css +++ b/src/App.css @@ -27,6 +27,12 @@ --app-accent: var(--tab-research-accent); } +.app--deadlines { + --app-tint: var(--tab-deadlines-tint); + --app-border: var(--tab-deadlines-border); + --app-accent: var(--tab-deadlines-accent); +} + .app--publication { --app-tint: var(--tab-publication-tint); --app-border: var(--tab-publication-border); diff --git a/src/components/Nav.jsx b/src/components/Nav.jsx index 9a6f8ee..f45a5d2 100644 --- a/src/components/Nav.jsx +++ b/src/components/Nav.jsx @@ -333,6 +333,10 @@ export default function Nav() { }, ], }, + { + key: "deadlines", + label: "Deadlines", + }, { key: "publication", label: "Publication", diff --git a/src/components/tabs/Deadlines.css b/src/components/tabs/Deadlines.css new file mode 100644 index 0000000..165ed11 --- /dev/null +++ b/src/components/tabs/Deadlines.css @@ -0,0 +1,295 @@ +.deadlines { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + gap: var(--section-gap); +} + +.deadlines__controls, +.deadlines__list { + width: 100%; +} + +.deadlines__filters { + padding-top: 0; +} + +.deadlines__filter-btn, +.deadlines__milestone-btn { + --btn-height-local: 2.05rem; + --btn-pad-x-local: 0.72rem; + --surface-hover-bg: var(--surface-hover-soft); + --surface-hover-border: var(--line-strong); + --surface-hover-shadow: var(--shadow-elev-1); + --surface-hover-translate: 0px; +} + +.deadlines__list { + display: grid; + gap: var(--section-gap); +} + +.deadlines__list-head { + display: grid; + gap: var(--section-head-gap); +} + +.deadlines__list-head h2 { + position: relative; + width: fit-content; + padding-bottom: var(--space-1p5); + font-size: var(--section-title-size); +} + +.deadlines__list-head h2::after { + content: ""; + position: absolute; + left: 0; + bottom: 0; + width: 2rem; + height: 2px; + border-radius: 999px; + background: var(--tab-identity-accent, var(--accent-ui-500)); +} + +.deadlines__list-head p { + margin: 0; + color: var(--text-muted); + font-size: var(--section-copy-size); +} + +.deadlines__venue { + display: grid; + gap: var(--row-gap); +} + +.deadlines__venue-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--row-gap); +} + +.deadlines__venue-identity { + min-width: 0; + display: grid; + gap: var(--space-1); +} + +.deadlines__venue-identity h2 { + font-size: var(--card-title-size); +} + +.deadlines__venue-identity > p { + margin: 0; + color: var(--text-muted); + font-size: var(--type-support-size); +} + +.deadlines__badges { + display: flex; + flex-wrap: wrap; + gap: var(--control-gap); +} + +.deadlines__badge { + width: fit-content; + padding: var(--chip-pad-y) var(--chip-pad-x); + border: 1px solid var(--outline-color); + border-radius: 999px; + color: var(--text-secondary); + background: var(--surface-2); + font-size: var(--badge-text-size); + font-weight: var(--type-badge-weight); + line-height: var(--type-badge-line-height); + text-transform: uppercase; +} + +.deadlines__badge--area { + color: var(--tab-deadlines-accent); + border-color: var(--tab-deadlines-border); + background: var(--tab-deadlines-tint); +} + +.deadlines__badge--verified { + color: #267a56; + border-color: #b9e1cf; + background: #edfbf3; +} + +.deadlines__badge--awaiting, +.deadlines__badge--review, +.deadlines__badge--estimated { + color: var(--text-secondary); +} + +.deadlines__official-link { + --btn-ink: var(--tab-deadlines-accent); + + flex: 0 0 auto; +} + +.deadlines__event { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)) auto; + align-items: end; + gap: var(--row-gap); + margin: 0; + padding: var(--space-3) 0; + border-top: 1px solid var(--tab-deadlines-border); + border-bottom: 1px solid var(--tab-deadlines-border); +} + +.deadlines__event-item { + min-width: 0; + display: grid; + gap: var(--space-0p5); +} + +.deadlines__event-item--source { + width: fit-content; +} + +.deadlines__event dt { + color: var(--text-muted); + font-size: var(--type-meta-size); + font-weight: 650; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.deadlines__event dd { + margin: 0; + color: var(--text-secondary); + font-size: var(--type-support-size); + line-height: 1.45; +} + +.deadlines__event-link { + color: var(--tab-deadlines-accent); + font-size: var(--type-meta-size); + font-weight: 650; + white-space: nowrap; +} + +.deadlines__schedule { + display: grid; + gap: var(--space-3); + padding-top: var(--space-3); +} + +.deadlines__milestone-tabs { + display: flex; + flex-wrap: wrap; + gap: var(--control-gap); +} + +.deadlines__milestone-btn.is-active { + --btn-bg: var(--tab-deadlines-tint); + --btn-border: var(--tab-deadlines-border); + --btn-ink: var(--tab-deadlines-accent); + --btn-shadow: var(--shadow-elev-1); +} + +.deadlines__milestone-detail { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--row-gap); + padding: var(--row-pad-y) var(--row-pad-x); + border: 1px solid var(--tab-deadlines-border); + border-radius: var(--radius-md); + background: linear-gradient(110deg, var(--tab-deadlines-tint), var(--surface) 66%); +} + +.deadlines__milestone-detail > div { + min-width: 0; + display: grid; + gap: var(--space-0p5); +} + +.deadlines__milestone-label, +.deadlines__date, +.deadlines__timezone-note, +.deadlines__countdown, +.deadlines__empty-schedule p, +.deadlines__source-meta { + margin: 0; +} + +.deadlines__milestone-label { + color: var(--text-primary); + font-size: var(--type-support-size); + font-weight: 650; +} + +.deadlines__date { + color: var(--text-secondary); + font-variant-numeric: tabular-nums; +} + +.deadlines__timezone-note, +.deadlines__source-meta { + color: var(--text-muted); + font-size: var(--type-meta-size); +} + +.deadlines__countdown { + flex: 0 0 auto; + color: var(--tab-deadlines-accent); + font-size: clamp(1.02rem, 2vw, 1.34rem); + font-weight: 720; + font-variant-numeric: tabular-nums; + letter-spacing: -0.02em; + white-space: nowrap; +} + +.deadlines__empty-schedule { + padding: var(--row-pad-y) var(--row-pad-x); + border: 1px dashed var(--tab-deadlines-border); + border-radius: var(--radius-md); + background: var(--surface-2); +} + +.deadlines__empty-schedule p { + color: var(--text-muted); + font-size: var(--type-support-size); +} + +.deadlines__source-meta { + display: flex; + flex-wrap: wrap; + gap: var(--space-1); +} + +.deadlines__source-meta span + span::before { + content: "·"; + margin-right: var(--space-1); +} + +@media (max-width: 52rem) { + .deadlines__filter-btn, + .deadlines__milestone-btn { + --btn-height-local: var(--target-min); + } + + .deadlines__venue-head, + .deadlines__milestone-detail { + flex-direction: column; + } + + .deadlines__event { + grid-template-columns: 1fr; + align-items: start; + gap: var(--space-2); + } + + .deadlines__official-link { + align-self: flex-start; + } + + .deadlines__countdown { + font-size: 1.04rem; + } +} diff --git a/src/components/tabs/Deadlines.jsx b/src/components/tabs/Deadlines.jsx new file mode 100644 index 0000000..54bd0a9 --- /dev/null +++ b/src/components/tabs/Deadlines.jsx @@ -0,0 +1,229 @@ +/* eslint-disable react/prop-types */ +import { useEffect, useMemo, useState } from "react"; +import { + DEADLINE_AREAS, + DEADLINE_DISPLAY_TIMEZONE, + formatDeadlineInDisplayTimezone, + formatSourceCheckedAt, + getAllVenues, + getCountdownLabel, + getDefaultMilestoneId, + getVenueStatusMeta, +} from "../../utils/deadlineData"; +import "./Deadlines.css"; + +const ALL_AREAS = "all"; + +const getSelectedMilestone = (venue, selectedMilestones, now) => { + const selectedId = selectedMilestones[venue.id] ?? getDefaultMilestoneId(venue, now); + return venue.milestones.find((milestone) => milestone.id === selectedId) ?? null; +}; + +function DeadlineCard({ venue, selectedMilestones, onSelectMilestone, now }) { + const selectedMilestone = getSelectedMilestone(venue, selectedMilestones, now); + const statusMeta = getVenueStatusMeta(venue.status); + const sourceCheck = venue.source_check; + const event = venue.event; + + return ( +
+
+
+
+ {venue.areas.map((area) => { + const areaMeta = DEADLINE_AREAS.find((item) => item.key === area); + return ( + + {areaMeta?.label ?? area} + + ); + })} + + {statusMeta.label} + +
+

{venue.name}

+

{venue.full_name}

+
+ + Official CFP + +
+ + {event ? ( +
+
+
Event dates
+
{event.dates}
+
+
+
Location
+
{event.location}
+
+
+
Venue
+
{event.venue}
+
+
+
Source
+
+ + Official event info + +
+
+
+ ) : null} + + {venue.milestones.length > 0 && selectedMilestone ? ( +
+
+ {venue.milestones.map((milestone) => { + const isSelected = milestone.id === selectedMilestone.id; + return ( + + ); + })} +
+
+
+

{selectedMilestone.label}

+

+ {formatDeadlineInDisplayTimezone(selectedMilestone.deadline_at)} KST +

+

+ Official deadline timezone: {selectedMilestone.timezone_label} +

+
+

+ {getCountdownLabel(selectedMilestone.deadline_at, now)} +

+
+
+ ) : ( +
+

Track-specific deadlines have not been published in the current official CFP.

+
+ )} + +
+ + {venue.source_checked_at + ? `Verified ${formatSourceCheckedAt(venue.source_checked_at)}` + : "Official CFP will be monitored when the current cycle opens."} + + {sourceCheck && sourceCheck.state !== "matched" ? ( + {sourceCheck.message} + ) : null} +
+
+ ); +} + +function Deadlines() { + const [selectedArea, setSelectedArea] = useState(ALL_AREAS); + const [selectedMilestones, setSelectedMilestones] = useState({}); + const [now, setNow] = useState(null); + const venues = useMemo(() => getAllVenues(), []); + + useEffect(() => { + const tick = () => setNow(new Date()); + tick(); + const intervalId = window.setInterval(tick, 1_000); + return () => window.clearInterval(intervalId); + }, []); + + const filteredVenues = useMemo( + () => + venues.filter( + (venue) => + selectedArea === ALL_AREAS || venue.areas.includes(selectedArea), + ), + [selectedArea, venues], + ); + + const handleSelectMilestone = (venueId, milestoneId) => { + setSelectedMilestones((current) => ({ + ...current, + [venueId]: milestoneId, + })); + }; + + return ( +
+
+

Deadlines

+

+ Official conference submission schedules for the lab’s core research areas. Countdown times are shown live in Korea Standard Time. +

+
+ +
+
+

Browse venues

+

Choose a research area, then switch between milestones inside each venue.

+
+
+ + {DEADLINE_AREAS.map((area) => ( + + ))} +
+
+ +
+
+
+

Conference schedule

+

{filteredVenues.length} venues · Times displayed in {DEADLINE_DISPLAY_TIMEZONE}

+
+
+ {filteredVenues.map((venue) => ( + + ))} +
+
+ ); +} + +export default Deadlines; diff --git a/src/generated/deadlines.generated.json b/src/generated/deadlines.generated.json new file mode 100644 index 0000000..9c1aacc --- /dev/null +++ b/src/generated/deadlines.generated.json @@ -0,0 +1,1039 @@ +{ + "meta": { + "title": "CVL-Lab conference deadlines", + "updated_at": "2026-08-12T09:00:00+09:00", + "display_timezone": "Asia/Seoul", + "area_order": [ + "computer-vision", + "machine-learning", + "llm-nlp", + "robotics" + ], + "area_labels": { + "computer-vision": "Computer Vision", + "machine-learning": "Machine Learning / AI", + "llm-nlp": "LLM / NLP", + "robotics": "Robotics" + } + }, + "venues": [ + { + "id": "cvpr", + "name": "CVPR", + "full_name": "IEEE/CVF Conference on Computer Vision and Pattern Recognition", + "areas": [ + "computer-vision" + ], + "official_url": "https://cvpr.thecvf.com/", + "cfp_url": "https://cvpr.thecvf.com/", + "status": "awaiting_cfp", + "event": { + "dates": "June 20-24, 2027", + "location": "Seattle, Washington, USA", + "venue": "Venue TBA", + "source_url": "https://www.thecvf.com/?p=137", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.373Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "df9cca766b463b89", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "iccv", + "name": "ICCV", + "full_name": "IEEE/CVF International Conference on Computer Vision", + "areas": [ + "computer-vision" + ], + "official_url": "https://iccv.thecvf.com/", + "cfp_url": "https://iccv.thecvf.com/", + "status": "awaiting_cfp", + "event": { + "dates": "October 2-8, 2027", + "location": "Hong Kong", + "venue": "Venue TBA", + "source_url": "https://www.thecvf.com/?p=137", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.394Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "21d8ae8ccbca0680", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "eccv", + "name": "ECCV", + "full_name": "European Conference on Computer Vision", + "areas": [ + "computer-vision" + ], + "official_url": "https://eccv.ecva.net/", + "cfp_url": "https://eccv.ecva.net/", + "status": "awaiting_cfp", + "event": { + "dates": "September 8-12, 2026", + "location": "Malmo, Sweden", + "venue": "Malmo Arena and Malmomassan", + "source_url": "https://eccv.ecva.net/Conferences/2026", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.395Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "87bf552c3c1aec01", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "wacv", + "name": "WACV 2027", + "full_name": "IEEE/CVF Winter Conference on Applications of Computer Vision", + "areas": [ + "computer-vision" + ], + "official_url": "https://wacv.thecvf.com/", + "cfp_url": "https://wacv.thecvf.com/", + "status": "verified", + "source_checked_at": "2026-08-12T09:00:00+09:00", + "source_evidence": "Round 2 Paper Submissions", + "event": { + "dates": "January 4-8, 2027", + "location": "Buena Vista, Florida, USA", + "venue": "Disney Springs", + "source_url": "https://wacv.thecvf.com/", + "status": "official" + }, + "milestones": [ + { + "id": "round-2-registration", + "label": "Round 2 registration", + "short_label": "Registration", + "kind": "registration", + "deadline_at": "2026-08-21T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 New Paper Registration.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + }, + { + "id": "round-2-paper", + "label": "Round 2 paper submission", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-08-28T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 Paper Submissions.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + }, + { + "id": "round-2-supplementary", + "label": "Round 2 supplementary material", + "short_label": "Supplementary", + "kind": "supplementary", + "deadline_at": "2026-08-30T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 Supplemental Material.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + }, + { + "id": "final-decision", + "label": "Final decisions released", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2026-10-09T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Round 2 Reviews and Final Decisions to Authors.{0,220}?([A-Z][a-z]{2}\\s+\\d{1,2}\\s+'\\d{2})", + "timezone": "AoE" + } + } + ], + "source_check": { + "checked_at": "2026-08-12T09:37:55.395Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "20f5e9a0b08c0b3e", + "extracted_milestones": { + "round-2-registration": "2026-08-21T23:59:00-12:00", + "round-2-paper": "2026-08-28T23:59:00-12:00", + "round-2-supplementary": "2026-08-30T23:59:00-12:00", + "final-decision": "2026-10-09T23:59:00-12:00" + }, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "bmvc", + "name": "BMVC", + "full_name": "British Machine Vision Conference", + "areas": [ + "computer-vision" + ], + "official_url": "https://bmvc2026.org/", + "cfp_url": "https://bmvc2026.org/", + "status": "awaiting_cfp", + "event": { + "dates": "November 23-26, 2026", + "location": "Lancaster, United Kingdom", + "venue": "Venue TBA", + "source_url": "https://www.bmva.org/bmvc", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.396Z", + "state": "unavailable", + "http_status": null, + "content_fingerprint": null, + "extracted_milestones": {}, + "message": "Official source could not be checked: fetch failed." + } + }, + { + "id": "accv", + "name": "ACCV", + "full_name": "Asian Conference on Computer Vision", + "areas": [ + "computer-vision" + ], + "official_url": "https://accv2026.org/", + "cfp_url": "https://accv2026.org/", + "status": "awaiting_cfp", + "event": { + "dates": "December 14-18, 2026", + "location": "Osaka, Japan", + "venue": "Venue TBA", + "source_url": "https://accv2026.org/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.396Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "1a466d0c3989c11f", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "3dv", + "name": "3DV", + "full_name": "International Conference on 3D Vision", + "areas": [ + "computer-vision" + ], + "official_url": "https://3dvconf.github.io/2027/", + "cfp_url": "https://3dvconf.github.io/2027/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Paper Submission", + "event": { + "dates": "April 6-9, 2027", + "location": "Thessaloniki, Greece", + "venue": "Thessaloniki Concert Hall (M2 building)", + "source_url": "https://3dvconf.github.io/2027/", + "status": "official" + }, + "milestones": [ + { + "id": "paper", + "label": "Paper submission", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-08-28T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Paper Submission\\s+([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "supplementary", + "label": "Supplementary material", + "short_label": "Supplementary", + "kind": "supplementary", + "deadline_at": "2026-09-02T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Supplementary Material\\s+([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "preliminary-notification", + "label": "Preliminary notification", + "short_label": "Prelim. decision", + "kind": "notification", + "deadline_at": "2026-10-27T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + }, + { + "id": "final-notification", + "label": "Final notification", + "short_label": "Final decision", + "kind": "notification", + "deadline_at": "2026-12-02T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ], + "source_check": { + "checked_at": "2026-08-12T09:37:55.396Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "6351c2ff68b3bc95", + "extracted_milestones": { + "paper": "2026-08-28T23:59:00-12:00", + "supplementary": "2026-09-02T23:59:00-12:00" + }, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "miccai", + "name": "MICCAI", + "full_name": "Medical Image Computing and Computer Assisted Intervention", + "areas": [ + "computer-vision" + ], + "official_url": "https://conferences.miccai.org/", + "cfp_url": "https://conferences.miccai.org/", + "status": "awaiting_cfp", + "event": { + "dates": "September 27-October 1, 2026", + "location": "Strasbourg, France", + "venue": "Strasbourg Convention Center", + "source_url": "https://conferences.miccai.org/2026/en/default.asp", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.397Z", + "state": "needs_review", + "http_status": 403, + "content_fingerprint": "3c8e4551b538b9c5", + "extracted_milestones": {}, + "message": "Official source returned HTTP 403." + } + }, + { + "id": "neurips", + "name": "NeurIPS", + "full_name": "Conference on Neural Information Processing Systems", + "areas": [ + "machine-learning", + "llm-nlp" + ], + "official_url": "https://neurips.cc/", + "cfp_url": "https://neurips.cc/", + "status": "awaiting_cfp", + "event": { + "dates": "December 6-13, 2026", + "location": "Sydney, Australia; Atlanta, Georgia, USA; Paris, France", + "venue": "Multi-site conference", + "source_url": "https://neurips.cc/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.397Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "8507f5d0ba16175e", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "icml", + "name": "ICML", + "full_name": "International Conference on Machine Learning", + "areas": [ + "machine-learning" + ], + "official_url": "https://icml.cc/", + "cfp_url": "https://icml.cc/", + "status": "awaiting_cfp", + "event": { + "dates": "July 6-11, 2026", + "location": "Seoul, South Korea", + "venue": "COEX Convention & Exhibition Center", + "source_url": "https://icml.cc/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.397Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "508fb44aaec5e9dc", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "iclr", + "name": "ICLR 2027", + "full_name": "International Conference on Learning Representations", + "areas": [ + "machine-learning", + "llm-nlp", + "robotics" + ], + "official_url": "https://iclr.cc/", + "cfp_url": "https://iclr.cc/Conferences/2027/AuthorGuidelines", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "full paper submission deadline", + "event": { + "dates": "October 6-9, 2026", + "location": "California, USA", + "venue": "Venue TBA", + "source_url": "https://iclr.cc/Conferences/2027", + "status": "official" + }, + "milestones": [ + { + "id": "abstract", + "label": "Abstract deadline", + "short_label": "Abstract", + "kind": "abstract", + "deadline_at": "2026-09-18T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "abstract submission deadline(?:\\s+of)?\\s+([A-Za-z]+\\s+\\d{1,2}(?:,)?\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "paper", + "label": "Paper deadline", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-09-25T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "full paper submission deadline(?:\\s+is)?\\s+([A-Za-z]+\\s+\\d{1,2}(?:,)?\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "reviews", + "label": "Reviews released", + "short_label": "Reviews", + "kind": "review", + "deadline_at": "2026-11-05T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + }, + { + "id": "decision", + "label": "Final decisions", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2026-12-16T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ], + "source_check": { + "checked_at": "2026-08-12T09:37:55.397Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "8f6749dff9eb3aaa", + "extracted_milestones": { + "abstract": "2026-09-18T23:59:00-12:00", + "paper": "2026-09-25T23:59:00-12:00" + }, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "aaai", + "name": "AAAI", + "full_name": "AAAI Conference on Artificial Intelligence", + "areas": [ + "machine-learning", + "llm-nlp", + "robotics" + ], + "official_url": "https://aaai.org/conference/aaai/aaai-27/", + "cfp_url": "https://aaai.org/conference/aaai/aaai-27/main-technical-track-call/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Full papers due", + "event": { + "dates": "February 16-23, 2027", + "location": "Montreal, Canada", + "venue": "Venue TBA", + "source_url": "https://aaai.org/conference/aaai/aaai-27/", + "status": "official" + }, + "milestones": [ + { + "id": "abstract", + "label": "Abstract deadline", + "short_label": "Abstract", + "kind": "abstract", + "deadline_at": "2026-07-21T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})\\s+Abstracts due", + "timezone": "AoE" + } + }, + { + "id": "paper", + "label": "Full paper deadline", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-07-28T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})\\s+Full papers due", + "timezone": "AoE" + } + }, + { + "id": "supplementary", + "label": "Supplementary material and code", + "short_label": "Supplementary", + "kind": "supplementary", + "deadline_at": "2026-07-31T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})\\s+Supplementary material and code due", + "timezone": "AoE" + } + }, + { + "id": "phase-1-decision", + "label": "Phase 1 rejection notification", + "short_label": "Phase 1 decision", + "kind": "notification", + "deadline_at": "2026-09-24T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + }, + { + "id": "final-decision", + "label": "Final acceptance notification", + "short_label": "Final decision", + "kind": "notification", + "deadline_at": "2026-11-30T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ], + "source_check": { + "checked_at": "2026-08-12T09:37:55.398Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "04dbde09822af144", + "extracted_milestones": { + "abstract": "2026-07-21T23:59:00-12:00", + "paper": "2026-07-28T23:59:00-12:00", + "supplementary": "2026-07-31T23:59:00-12:00" + }, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "aistats", + "name": "AISTATS", + "full_name": "International Conference on Artificial Intelligence and Statistics", + "areas": [ + "machine-learning" + ], + "official_url": "https://aistats.org/", + "cfp_url": "https://aistats.org/", + "status": "awaiting_cfp", + "event": { + "dates": "May 2-5, 2026", + "location": "Tangier, Morocco", + "venue": "Hilton Tangier Al Houara Resort & Spa", + "source_url": "https://virtual.aistats.org/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.398Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "8577f2e2b650e82f", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "uai", + "name": "UAI", + "full_name": "Conference on Uncertainty in Artificial Intelligence", + "areas": [ + "machine-learning" + ], + "official_url": "https://www.auai.org/uai/", + "cfp_url": "https://www.auai.org/uai/", + "status": "awaiting_cfp", + "event": { + "dates": "August 17-21, 2026", + "location": "Amsterdam, Netherlands", + "venue": "KIT Events", + "source_url": "https://www.auai.org/uai2026/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.398Z", + "state": "needs_review", + "http_status": 404, + "content_fingerprint": "b8c1d648ac198b1e", + "extracted_milestones": {}, + "message": "Official source returned HTTP 404." + } + }, + { + "id": "acl", + "name": "ACL", + "full_name": "Annual Meeting of the Association for Computational Linguistics", + "areas": [ + "llm-nlp" + ], + "official_url": "https://www.aclweb.org/portal/", + "cfp_url": "https://www.aclweb.org/portal/", + "status": "awaiting_cfp", + "event": { + "dates": "August 17-22, 2027", + "location": "Kyoto, Japan", + "venue": "Venue TBA", + "source_url": "https://2027.aclweb.org/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.399Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "53f6aae209dd3a9c", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "emnlp", + "name": "EMNLP", + "full_name": "Conference on Empirical Methods in Natural Language Processing", + "areas": [ + "llm-nlp" + ], + "official_url": "https://2026.emnlp.org/", + "cfp_url": "https://2026.emnlp.org/", + "status": "awaiting_cfp", + "event": { + "dates": "October 22-29, 2026", + "location": "Budapest, Hungary", + "venue": "Hungexpo", + "source_url": "https://2026.emnlp.org/venue/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.399Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "f0a2284452494587", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "naacl", + "name": "NAACL", + "full_name": "North American Chapter of the Association for Computational Linguistics", + "areas": [ + "llm-nlp" + ], + "official_url": "https://2027.naacl.org/", + "cfp_url": "https://2027.naacl.org/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Submission deadline (ARR)", + "event": { + "dates": "June 1-5, 2027", + "location": "San Francisco, California, USA", + "venue": "Venue TBA", + "source_url": "https://2027.naacl.org/", + "status": "official" + }, + "milestones": [ + { + "id": "arr-submission", + "label": "ARR submission deadline", + "short_label": "ARR submission", + "kind": "paper", + "deadline_at": "2026-10-12T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "Submission deadline \\(ARR\\)[^A-Za-z]{0,20}([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + } + ], + "source_check": { + "checked_at": "2026-08-12T09:37:55.399Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "25b93a93dcbd5100", + "extracted_milestones": { + "arr-submission": "2026-10-12T23:59:00-12:00" + }, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "eacl", + "name": "EACL", + "full_name": "European Chapter of the Association for Computational Linguistics", + "areas": [ + "llm-nlp" + ], + "official_url": "https://2027.eacl.org/", + "cfp_url": "https://2027.eacl.org/calls/papers/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "ARR submission deadline", + "event": { + "dates": "March 9-14, 2027", + "location": "Athens, Greece", + "venue": "Venue TBA", + "source_url": "https://2027.eacl.org/", + "status": "official" + }, + "milestones": [ + { + "id": "arr-submission", + "label": "ARR submission deadline", + "short_label": "ARR submission", + "kind": "paper", + "deadline_at": "2026-08-03T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "ARR submission deadline \\(long (?:&|&) short papers\\)[^A-Za-z]{0,20}([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "commitment", + "label": "EACL commitment deadline", + "short_label": "Commitment", + "kind": "paper", + "deadline_at": "2026-10-11T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified", + "source_parser": { + "match": "EACL 2027 commitment deadline[^A-Za-z]{0,20}([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})", + "timezone": "AoE" + } + }, + { + "id": "notification", + "label": "Acceptance notification", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2026-11-12T23:59:00-12:00", + "timezone_label": "AoE", + "status": "verified" + } + ], + "source_check": { + "checked_at": "2026-08-12T09:37:55.399Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "f3c439b4df339ad5", + "extracted_milestones": { + "arr-submission": "2026-08-03T23:59:00-12:00", + "commitment": "2026-10-11T23:59:00-12:00" + }, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "aacl", + "name": "AACL", + "full_name": "Asia-Pacific Chapter of the Association for Computational Linguistics", + "areas": [ + "llm-nlp" + ], + "official_url": "https://www.aaclweb.org/", + "cfp_url": "https://www.aaclweb.org/", + "status": "awaiting_cfp", + "event": { + "dates": "November 6-10, 2026", + "location": "Hengqin, China", + "venue": "Venue TBA", + "source_url": "https://2026.aaclnet.org/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.400Z", + "state": "unavailable", + "http_status": null, + "content_fingerprint": null, + "extracted_milestones": {}, + "message": "Official source could not be checked: fetch failed." + } + }, + { + "id": "coling", + "name": "COLING", + "full_name": "International Conference on Computational Linguistics", + "areas": [ + "llm-nlp" + ], + "official_url": "https://coling2025.org/", + "cfp_url": "https://coling2025.org/", + "status": "awaiting_cfp", + "event": { + "dates": "May 9-14, 2027", + "location": "Macau, China", + "venue": "Venue TBA", + "source_url": "https://2027.coling-iccl.org/about/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.400Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "79dd6dd6681be131", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "conll", + "name": "CoNLL", + "full_name": "Conference on Computational Natural Language Learning", + "areas": [ + "llm-nlp" + ], + "official_url": "https://www.conll.org/", + "cfp_url": "https://www.conll.org/", + "status": "awaiting_cfp", + "event": { + "dates": "July 3-4, 2026", + "location": "San Diego, California, USA", + "venue": "Co-located with ACL 2026", + "source_url": "https://www.conll.org/2026", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.400Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "95348c1e4d2eb4e5", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "icra", + "name": "ICRA", + "full_name": "IEEE International Conference on Robotics and Automation", + "areas": [ + "robotics" + ], + "official_url": "https://2027.ieee-icra.org/", + "cfp_url": "https://2027.ieee-icra.org/announcements/call-for-technical-papers/", + "status": "verified", + "source_checked_at": "2026-08-12T18:30:00+09:00", + "source_evidence": "Contributed paper submission deadline", + "event": { + "dates": "May 24-28, 2027", + "location": "Seoul, South Korea", + "venue": "COEX", + "source_url": "https://2027.ieee-icra.org/", + "status": "official" + }, + "milestones": [ + { + "id": "paper", + "label": "Contributed paper submission", + "short_label": "Paper", + "kind": "paper", + "deadline_at": "2026-09-15T23:59:00-07:00", + "timezone_label": "Pacific Time (PT)", + "status": "verified", + "source_parser": { + "match": "([A-Za-z]+\\s+\\d{1,2},\\s+\\d{4})[\\s\\S]{0,80}11:59[\\s\\S]{0,80}Contributed paper submission deadline", + "timezone": "America/Los_Angeles" + } + }, + { + "id": "decision", + "label": "Notification of decision", + "short_label": "Decision", + "kind": "notification", + "deadline_at": "2027-01-31T23:59:00-08:00", + "timezone_label": "Pacific Time (PT)", + "status": "verified" + } + ], + "source_check": { + "checked_at": "2026-08-12T09:37:55.400Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "83952fb1efda6869", + "extracted_milestones": { + "paper": "2026-09-15T23:59:00-07:00" + }, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "iros", + "name": "IROS", + "full_name": "IEEE/RSJ International Conference on Intelligent Robots and Systems", + "areas": [ + "robotics" + ], + "official_url": "https://www.ieee-ras.org/event/2027-ieee-rsj-international-conference-on-intelligent-robots-and-systems-iros-70525/", + "cfp_url": "https://www.ieee-ras.org/event/2027-ieee-rsj-international-conference-on-intelligent-robots-and-systems-iros-70525/", + "status": "awaiting_cfp", + "event": { + "dates": "September 26-October 1, 2027", + "location": "Florence, Italy", + "venue": "Fortezza da Basso", + "source_url": "https://www.ieee-ras.org/event/2027-ieee-rsj-international-conference-on-intelligent-robots-and-systems-iros-70525/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.401Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "4597fda4cef8e8ee", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "rss", + "name": "RSS", + "full_name": "Robotics: Science and Systems", + "areas": [ + "robotics" + ], + "official_url": "https://roboticsconference.org/", + "cfp_url": "https://roboticsconference.org/", + "status": "awaiting_cfp", + "event": { + "dates": "July 13-17, 2026", + "location": "Sydney, Australia", + "venue": "University of Technology Sydney and ICC Sydney", + "source_url": "https://roboticsconference.org/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.401Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "77165ff831242873", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "corl", + "name": "CoRL", + "full_name": "Conference on Robot Learning", + "areas": [ + "robotics", + "machine-learning" + ], + "official_url": "https://www.corl.org/", + "cfp_url": "https://www.corl.org/", + "status": "awaiting_cfp", + "event": { + "dates": "November 9-12, 2026", + "location": "Austin, Texas, USA", + "venue": "JW Marriott Austin, Hilton Austin, and Bass Hall", + "source_url": "https://www.corl.org/home", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.401Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "50dadf680b736f1b", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + }, + { + "id": "humanoids", + "name": "Humanoids", + "full_name": "IEEE-RAS International Conference on Humanoid Robots", + "areas": [ + "robotics" + ], + "official_url": "https://www.ieee-ras.org/conferences-workshops/financially-co-sponsored/humanoids", + "cfp_url": "https://www.ieee-ras.org/conferences-workshops/financially-co-sponsored/humanoids", + "status": "awaiting_cfp", + "event": { + "dates": "December 7-9, 2026", + "location": "Santa Clara, California, USA", + "venue": "Santa Clara Convention Center", + "source_url": "https://www.ieee-ras.org/event/2026-ieee-ras-25th-international-conference-on-humanoid-robots-humanoids-68892/", + "status": "official" + }, + "milestones": [], + "source_check": { + "checked_at": "2026-08-12T09:37:55.401Z", + "state": "matched", + "http_status": 200, + "content_fingerprint": "673d1d958ed36078", + "extracted_milestones": {}, + "message": "Official source matched the configured evidence." + } + } + ] +} diff --git a/src/index.css b/src/index.css index 9c5a4ce..2e46959 100644 --- a/src/index.css +++ b/src/index.css @@ -108,6 +108,7 @@ section { .home, .news-page, .research-wrapper, + .deadlines, .publication, .people, .photo-wrapper, diff --git a/src/pages/DeadlinesPage.jsx b/src/pages/DeadlinesPage.jsx new file mode 100644 index 0000000..774b153 --- /dev/null +++ b/src/pages/DeadlinesPage.jsx @@ -0,0 +1,7 @@ +import Deadlines from "../components/tabs/Deadlines"; + +function DeadlinesPage() { + return ; +} + +export default DeadlinesPage; diff --git a/src/routes/pageManifest.jsx b/src/routes/pageManifest.jsx index be383e8..9e160ff 100644 --- a/src/routes/pageManifest.jsx +++ b/src/routes/pageManifest.jsx @@ -4,6 +4,7 @@ import { ROUTE_DEFINITIONS, SSG_ROUTE_PATHS } from "./routeDefinitions"; const HomePage = lazy(() => import("../pages/HomePage")); const NewsPage = lazy(() => import("../pages/NewsPage")); const ResearchPage = lazy(() => import("../pages/ResearchPage")); +const DeadlinesPage = lazy(() => import("../pages/DeadlinesPage")); const PublicationPage = lazy(() => import("../pages/PublicationPage")); const PeoplePage = lazy(() => import("../pages/PeoplePage")); const PhotoPage = lazy(() => import("../pages/PhotoPage")); @@ -14,6 +15,7 @@ const PAGE_COMPONENTS = { home: HomePage, news: NewsPage, research: ResearchPage, + deadlines: DeadlinesPage, publication: PublicationPage, people: PeoplePage, photo: PhotoPage, diff --git a/src/routes/pageManifest.ssg.jsx b/src/routes/pageManifest.ssg.jsx index 95cba2c..aaeeebe 100644 --- a/src/routes/pageManifest.ssg.jsx +++ b/src/routes/pageManifest.ssg.jsx @@ -2,6 +2,7 @@ import { ROUTE_DEFINITIONS, SSG_ROUTE_PATHS } from "./routeDefinitions"; import HomePage from "../pages/HomePage"; import NewsPage from "../pages/NewsPage"; import ResearchPage from "../pages/ResearchPage"; +import DeadlinesPage from "../pages/DeadlinesPage"; import PublicationPage from "../pages/PublicationPage"; import PeoplePage from "../pages/PeoplePage"; import PhotoPage from "../pages/PhotoPage"; @@ -12,6 +13,7 @@ const PAGE_COMPONENTS = { home: HomePage, news: NewsPage, research: ResearchPage, + deadlines: DeadlinesPage, publication: PublicationPage, people: PeoplePage, photo: PhotoPage, diff --git a/src/routes/routeDefinitions.js b/src/routes/routeDefinitions.js index 0f44b5d..8f5d632 100644 --- a/src/routes/routeDefinitions.js +++ b/src/routes/routeDefinitions.js @@ -12,6 +12,7 @@ export const ROUTE_DEFINITIONS = [ { path: "/news", tabKey: "news" }, { path: "/research", tabKey: "research" }, ...RESEARCH_ROUTE_PATHS.map((path) => ({ path, tabKey: "research" })), + { path: "/deadlines", tabKey: "deadlines" }, { path: "/publication", tabKey: "publication" }, { path: "/people", tabKey: "people" }, { path: "/photo", tabKey: "photo" }, diff --git a/src/routes/routeUtils.js b/src/routes/routeUtils.js index ed40bfe..6873a04 100644 --- a/src/routes/routeUtils.js +++ b/src/routes/routeUtils.js @@ -2,6 +2,7 @@ export const TAB_KEYS = [ "home", "news", "research", + "deadlines", "publication", "people", "photo", diff --git a/src/styles/design-tokens.css b/src/styles/design-tokens.css index efbfb59..c33ba6a 100644 --- a/src/styles/design-tokens.css +++ b/src/styles/design-tokens.css @@ -156,6 +156,8 @@ --identity-photo-accent: #73beb3; --identity-contact-soft: #fff0f5; --identity-contact-accent: #e3a0b8; + --identity-deadlines-soft: #edf4ff; + --identity-deadlines-accent: #6d95d5; --tab-home-tint: #edf8f4; --tab-home-border: #cde9dd; @@ -185,6 +187,10 @@ --tab-contact-border: #f3dbe5; --tab-contact-accent: var(--identity-contact-accent); + --tab-deadlines-tint: var(--identity-deadlines-soft); + --tab-deadlines-border: #d0def4; + --tab-deadlines-accent: var(--identity-deadlines-accent); + --font-base: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif; @@ -385,6 +391,8 @@ --identity-photo-accent: #98d8cf; --identity-contact-soft: #3a1f30; --identity-contact-accent: #f0bdd1; + --identity-deadlines-soft: #192b48; + --identity-deadlines-accent: #a6c6ff; --tab-home-tint: #142f2d; --tab-home-border: #2f5f57; @@ -396,6 +404,7 @@ --tab-people-border: #544076; --tab-photo-border: #3d6166; --tab-contact-border: #6d4260; + --tab-deadlines-border: #3b5d92; --backdrop-line: rgba(154, 192, 255, 0.11); --backdrop-line-strong: rgba(154, 192, 255, 0.2); --backdrop-band: color-mix( diff --git a/src/utils/deadlineData.js b/src/utils/deadlineData.js new file mode 100644 index 0000000..e80563a2 --- /dev/null +++ b/src/utils/deadlineData.js @@ -0,0 +1,105 @@ +import DEADLINE_DATA from "../generated/deadlines.generated.json"; + +const FALLBACK_AREA_LABEL = "Other"; + +export const DEADLINE_AREAS = (DEADLINE_DATA.meta?.area_order ?? []).map( + (key) => ({ + key, + label: DEADLINE_DATA.meta?.area_labels?.[key] ?? FALLBACK_AREA_LABEL, + }), +); + +export const DEADLINE_DISPLAY_TIMEZONE = + DEADLINE_DATA.meta?.display_timezone ?? "Asia/Seoul"; + +export const DEADLINE_STATUS_META = { + verified: { label: "Official CFP verified", tone: "verified" }, + awaiting_cfp: { label: "Awaiting current CFP", tone: "awaiting" }, + needs_review: { label: "Source needs review", tone: "review" }, + estimated: { label: "Estimated", tone: "estimated" }, +}; + +const asDate = (value) => { + const timestamp = Date.parse(value); + return Number.isNaN(timestamp) ? null : new Date(timestamp); +}; + +export const getVenueStatusMeta = (status) => + DEADLINE_STATUS_META[status] ?? { + label: "Source status unavailable", + tone: "awaiting", + }; + +export const getAllVenues = () => + [...(DEADLINE_DATA.venues ?? [])] + .map((venue) => ({ + ...venue, + milestones: [...(venue.milestones ?? [])].sort( + (left, right) => + Date.parse(left.deadline_at) - Date.parse(right.deadline_at), + ), + })) + .sort((left, right) => { + const leftHasMilestones = left.milestones.length > 0; + const rightHasMilestones = right.milestones.length > 0; + if (leftHasMilestones !== rightHasMilestones) { + return leftHasMilestones ? -1 : 1; + } + return left.name.localeCompare(right.name); + }); + +export const getDefaultMilestoneId = (venue, now = new Date()) => { + const nextMilestone = venue.milestones?.find((milestone) => { + const deadline = asDate(milestone.deadline_at); + return deadline && deadline > now; + }); + + return nextMilestone?.id ?? venue.milestones?.[0]?.id ?? null; +}; + +export const formatDeadlineInDisplayTimezone = (deadlineAt) => { + const date = asDate(deadlineAt); + if (!date) { + return "Date unavailable"; + } + + return new Intl.DateTimeFormat("en-US", { + dateStyle: "medium", + timeStyle: "short", + timeZone: DEADLINE_DISPLAY_TIMEZONE, + }).format(date); +}; + +export const formatSourceCheckedAt = (checkedAt) => { + const date = asDate(checkedAt); + if (!date) { + return "Not checked yet"; + } + + return new Intl.DateTimeFormat("en-US", { + month: "short", + day: "numeric", + year: "numeric", + timeZone: DEADLINE_DISPLAY_TIMEZONE, + }).format(date); +}; + +export const getCountdownLabel = (deadlineAt, now) => { + const deadline = asDate(deadlineAt); + if (!deadline || !now) { + return "Calculating…"; + } + + const remainingMs = deadline.getTime() - now.getTime(); + if (remainingMs <= 0) { + return "Closed"; + } + + const totalSeconds = Math.floor(remainingMs / 1000); + const days = Math.floor(totalSeconds / 86_400); + const hours = Math.floor((totalSeconds % 86_400) / 3_600); + const minutes = Math.floor((totalSeconds % 3_600) / 60); + const seconds = totalSeconds % 60; + + return `${days}d ${String(hours).padStart(2, "0")}h ${String(minutes).padStart(2, "0")}m ${String(seconds).padStart(2, "0")}s`; +};