This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

pkgs.timeful: 0-unstable-2026-10-01 -> 0-unstable-2026-10-07, add updateScript

+119 -111
+11 -8
pkgs/timeful/package.nix
··· 8 8 }: 9 9 10 10 let 11 - version = "0-unstable-2026-10-01"; 11 + version = "0-unstable-2026-10-07"; 12 12 13 13 # self-host.patch (AGPL-3.0 §13: this directory is the corresponding source 14 14 # linked from the footer and the landing page): 15 - # - strips Google Tag Manager, Publift/Primis ads, the paywall and the 16 - # landing page's YouTube/Vimeo embeds and GitHub star button 15 + # - strips Google Tag Manager and the landing page's YouTube/Vimeo embeds 16 + # and GitHub star button (upstream itself dropped ads and the paywall) 17 17 # - replaces the Google/Outlook/email-OTP sign-in page with GitHub OAuth 18 18 # (GET /api/auth/github, GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET) 19 19 # - "Schedule" offers a .ics download next to Google Calendar/Outlook, and ··· 24 24 src = fetchFromGitHub { 25 25 owner = "schej-it"; 26 26 repo = "timeful.app"; 27 - rev = "23b7e7ead1d279ea5d8f431199a230c2dfbe0c1a"; 28 - hash = "sha256-Yp2dkz4+QZ9pqZXOM4srZsCiu/TGLQXGNZtsRd5Dx+U="; 27 + rev = "add892487de2e1a4cf0d68aa6c07518a379b8f41"; 28 + hash = "sha256-LQt1kBr2pSkmIxpwV5hSUqDXNt6vRjouCbMBsYqnVeg="; 29 29 }; 30 30 patches = [ ./self-host.patch ]; 31 31 }; ··· 35 35 inherit version; 36 36 src = "${src}/frontend"; 37 37 38 - npmDepsHash = "sha256-eHitoSJUPX2mhlOgGrECCUp0zhKFxgsfxkY7+BmeHak="; 38 + npmDepsHash = "sha256-P0oM7gKDfrNP5m5TRHGgPUb6kGLqMeCgLAuQVcDxOCQ="; 39 39 40 40 # No PostHog key and no Google/Microsoft OAuth client baked in; sign-in 41 41 # goes through the server-side GitHub flow instead. ··· 52 52 53 53 modRoot = "server"; 54 54 subPackages = [ "." ]; 55 - vendorHash = "sha256-dl9JURh6pCB7P+jAWcjlPZwmkKPqV7EaOJZSwJYwcFQ="; 55 + vendorHash = "sha256-1LtyEruDRoW8JUbxqkv+XGhRZolPIquuTQZwu5USYDg="; 56 56 57 57 nativeBuildInputs = [ makeWrapper ]; 58 58 ··· 61 61 wrapProgram $out/bin/timeful --set-default FRONTEND_DIST ${frontend} 62 62 ''; 63 63 64 - passthru = { inherit frontend; }; 64 + passthru = { 65 + inherit frontend; 66 + updateScript = ./update.sh; 67 + }; 65 68 66 69 meta = { 67 70 description = "Availability poll to find the best time for a group to meet (formerly Schej)";
+37 -103
pkgs/timeful/self-host.patch
··· 1 1 diff --git a/frontend/public/index.html b/frontend/public/index.html 2 - index a300a8a4..48cb0e61 100644 2 + index 19d37d75..20d2d18c 100644 3 3 --- a/frontend/public/index.html 4 4 +++ b/frontend/public/index.html 5 - @@ -1,21 +1,6 @@ 5 + @@ -1,20 +1,6 @@ 6 6 <!DOCTYPE html> 7 7 <html lang="en"> 8 8 <head> ··· 20 20 - })(window, document, "script", "dataLayer", "GTM-M677X6V") 21 21 - </script> 22 22 - <!-- End Google Tag Manager --> 23 - - <script async src="https://cdn.fuseplatform.net/publift/tags/2/4241/fuse.js"></script> 24 23 25 24 <meta charset="utf-8" /> 26 25 <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 27 - @@ -59,16 +44,6 @@ 26 + @@ -58,16 +44,6 @@ 28 27 /> 29 28 </head> 30 29 <body> ··· 42 41 <strong 43 42 >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work 44 43 diff --git a/frontend/src/App.vue b/frontend/src/App.vue 45 - index 176178b5..01469b17 100644 44 + index 0e022231..860c3221 100644 46 45 --- a/frontend/src/App.vue 47 46 +++ b/frontend/src/App.vue 48 - @@ -357,7 +357,10 @@ export default { 47 + @@ -329,7 +329,10 @@ export default { 49 48 this.webviewDialog = true 50 49 return 51 50 } ··· 58 57 } else { 59 58 this.$router.push({ name: "sign-in" }) 60 59 diff --git a/frontend/src/components/Footer.vue b/frontend/src/components/Footer.vue 61 - index 35d9a6d5..ed818994 100644 60 + index 0a528125..cce8bad1 100644 62 61 --- a/frontend/src/components/Footer.vue 63 62 +++ b/frontend/src/components/Footer.vue 64 63 @@ -30,7 +30,7 @@ ··· 70 69 aria-label="GitHub" 71 70 target="_blank" 72 71 ><v-icon class="tw-text-white/80 hover:tw-text-white" 73 - diff --git a/frontend/src/components/home/Dashboard.vue b/frontend/src/components/home/Dashboard.vue 74 - index c56eebff..af75f247 100644 75 - --- a/frontend/src/components/home/Dashboard.vue 76 - +++ b/frontend/src/components/home/Dashboard.vue 77 - @@ -8,7 +8,7 @@ 78 - Dashboard 79 - </div> 80 - <div 81 - - v-if="!isPremiumUser" 82 - + v-if="enablePaywall && !isPremiumUser" 83 - class="tw-flex tw-items-baseline tw-gap-2 tw-text-sm tw-font-normal tw-text-very-dark-gray" 84 - > 85 - <div> 86 - @@ -240,7 +240,13 @@ export default { 87 - }, 88 - computed: { 89 - ...mapGetters(["isPremiumUser"]), 90 - - ...mapState(["authUser", "events", "groupsEnabled", "folders"]), 91 - + ...mapState([ 92 - + "authUser", 93 - + "events", 94 - + "groupsEnabled", 95 - + "folders", 96 - + "enablePaywall", 97 - + ]), 98 - orderedFolders() { 99 - return this.folders.sort((a, b) => { 100 - return a.name.localeCompare(b.name) 101 72 diff --git a/frontend/src/components/schedule_overlap/ScheduleOverlap.vue b/frontend/src/components/schedule_overlap/ScheduleOverlap.vue 102 - index 1e028082..d7377fa3 100644 73 + index cee7fb53..848f48a6 100644 103 74 --- a/frontend/src/components/schedule_overlap/ScheduleOverlap.vue 104 75 +++ b/frontend/src/components/schedule_overlap/ScheduleOverlap.vue 105 - @@ -1048,6 +1048,7 @@ import { 106 - isPremiumUser, 76 + @@ -1033,6 +1033,7 @@ import { 77 + timeNumToTimeString, 107 78 prefersStartOnMonday, 108 79 } from "@/utils" 109 80 +import { buildIcs, downloadIcs } from "@/utils/ics_utils" 110 81 import { 111 82 availabilityTypes, 112 83 calendarOptionsDefaults, 113 - @@ -1250,11 +1251,7 @@ export default { 114 - ...mapState(["authUser", "overlayAvailabilitiesEnabled"]), 115 - ...mapGetters(["isPremiumUser"]), 116 - showAds() { 117 - - return ( 118 - - !this.ownerIsPremium && 119 - - !this.isPremiumUser && 120 - - this.state !== this.states.SET_SPECIFIC_TIMES 121 - - ) 122 - + return false 123 - }, 124 - /** Returns the width of the right side of the calendar */ 125 - rightSideWidth() { 126 - @@ -2076,7 +2073,7 @@ export default { 84 + @@ -2049,7 +2050,7 @@ export default { 127 85 } 128 86 return `Tap and drag to add your "available" ${daysOrTimes} in green.` 129 87 case this.states.SCHEDULE_EVENT: ··· 132 90 default: 133 91 return "" 134 92 } 135 - @@ -2092,7 +2089,7 @@ export default { 93 + @@ -2065,7 +2066,7 @@ export default { 136 94 } 137 95 return `Click and drag to add your "available" ${daysOrTimes} in green.` 138 96 case this.states.SCHEDULE_EVENT: ··· 141 99 default: 142 100 return "" 143 101 } 144 - @@ -3563,8 +3560,11 @@ export default { 102 + @@ -3536,8 +3537,11 @@ export default { 145 103 this.state = this.defaultState 146 104 }, 147 105 ··· 153 111 + */ 154 112 + confirmScheduleEvent(target = "google") { 155 113 if (!this.curScheduledEvent) return 156 - // if (!isPremiumUser(this.authUser)) { 157 - // this.showUpgradeDialog({ 158 - @@ -3615,9 +3615,27 @@ export default { 114 + 115 + this.$posthog.capture("schedule_event_confirmed") 116 + @@ -3579,9 +3583,27 @@ export default { 159 117 const emailsString = encodeURIComponent(emails.filter(Boolean).join(",")) 160 118 161 119 const eventId = this.event.shortId ?? this.event._id ··· 184 142 // Format start and end date to be in the format required by gcal (remove -, :, and .000) 185 143 const start = startDate.toISOString().replace(/([-:]|\.000)/g, "") 186 144 const end = endDate.toISOString().replace(/([-:]|\.000)/g, "") 187 - @@ -3626,14 +3644,13 @@ export default { 145 + @@ -3590,14 +3612,13 @@ export default { 188 146 url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent( 189 147 this.event.name 190 148 )}&dates=${start}/${end}&details=${encodeURIComponent( ··· 203 161 this.event.location || "" 204 162 )}&path=/calendar/action/compose&timezone=${this.curTimezone.value}` 205 163 diff --git a/frontend/src/components/schedule_overlap/ToolRow.vue b/frontend/src/components/schedule_overlap/ToolRow.vue 206 - index d8aaaddd..5822dd28 100644 164 + index c2c680e9..fa3c7353 100644 207 165 --- a/frontend/src/components/schedule_overlap/ToolRow.vue 208 166 +++ b/frontend/src/components/schedule_overlap/ToolRow.vue 209 167 @@ -118,7 +118,9 @@ ··· 267 225 // Site Metadata 268 226 Vue.use(VueMeta) 269 227 270 - diff --git a/frontend/src/store/index.js b/frontend/src/store/index.js 271 - index a0770841..b9460a8f 100644 272 - --- a/frontend/src/store/index.js 273 - +++ b/frontend/src/store/index.js 274 - @@ -29,7 +29,7 @@ export default new Vuex.Store({ 275 - signUpFormEnabled: false, 276 - daysOnlyEnabled: true, 277 - overlayAvailabilitiesEnabled: true, 278 - - enablePaywall: true, 279 - + enablePaywall: false, 280 - 281 - // Experiments 282 - pricingPageConversion: "control", 283 228 diff --git a/frontend/src/utils/ics_utils.js b/frontend/src/utils/ics_utils.js 284 229 new file mode 100644 285 230 index 00000000..c285bd1b ··· 356 301 + URL.revokeObjectURL(href) 357 302 +} 358 303 diff --git a/frontend/src/views/Event.vue b/frontend/src/views/Event.vue 359 - index 81887280..87dba8d1 100644 304 + index 1c43936d..1c4af8bd 100644 360 305 --- a/frontend/src/views/Event.vue 361 306 +++ b/frontend/src/views/Event.vue 362 - @@ -463,13 +463,29 @@ 307 + @@ -406,13 +406,29 @@ 363 308 Cancel 364 309 </v-btn> 365 310 <v-spacer /> ··· 395 340 + </v-menu> 396 341 </template> 397 342 </div> 398 - <PubliftAd 399 - @@ -638,11 +654,7 @@ export default { 400 - ...mapState(["authUser", "events"]), 401 - ...mapGetters(["isPremiumUser"]), 402 - showAds() { 403 - - return ( 404 - - !this.ownerIsPremium && 405 - - !this.isPremiumUser && 406 - - !this.isSettingSpecificTimes 407 - - ) 408 - + return false 409 - }, 410 - allowScheduleEvent() { 411 - return this.scheduleOverlapComponent?.allowScheduleEvent 412 - @@ -998,8 +1010,8 @@ export default { 343 + </div> 344 + @@ -859,8 +875,8 @@ export default { 413 345 cancelScheduleEvent() { 414 346 this.scheduleOverlapComponent?.cancelScheduleEvent() 415 347 }, ··· 421 353 422 354 highlightAvailabilityBtn() { 423 355 diff --git a/frontend/src/views/Landing.vue b/frontend/src/views/Landing.vue 424 - index b8a50d8f..2c47ac54 100644 356 + index 94a53c5f..6afdb561 100644 425 357 --- a/frontend/src/views/Landing.vue 426 358 +++ b/frontend/src/views/Landing.vue 427 - @@ -31,13 +31,10 @@ 359 + @@ -43,13 +43,10 @@ 428 360 class="tw-mb-4 tw-flex tw-select-none tw-items-center tw-rounded-full tw-border tw-border-light-gray-stroke tw-bg-white/70 tw-px-2.5 tw-py-1.5 tw-text-sm tw-text-dark-gray" 429 361 > 430 362 We're open source! ··· 442 374 > 443 375 </div> 444 376 <div 445 - @@ -111,20 +108,6 @@ 377 + @@ -123,20 +120,6 @@ 446 378 transition="fade-transition" 447 379 contain 448 380 /> ··· 463 395 </div> 464 396 </div> 465 397 </div> 466 - @@ -168,25 +151,6 @@ 398 + @@ -180,25 +163,6 @@ 467 399 /> 468 400 </div> 469 401 ··· 489 421 <!-- Reddit Testimonials --> 490 422 <div class="tw-flex tw-justify-center tw-bg-light-gray tw-py-12"> 491 423 <div class="tw-mx-4 tw-max-w-3xl tw-flex-1 sm:tw-mx-16"> 492 - @@ -299,11 +263,9 @@ import NewEvent from "@/components/NewEvent.vue" 424 + @@ -311,11 +275,9 @@ import NewEvent from "@/components/NewEvent.vue" 493 425 import NewDialog from "@/components/NewDialog.vue" 494 426 import LandingPageHeader from "@/components/landing/LandingPageHeader.vue" 495 427 import Logo from "@/components/Logo.vue" ··· 501 433 import Footer from "@/components/Footer.vue" 502 434 import PronunciationMenu from "@/components/PronunciationMenu.vue" 503 435 import { mapState, mapMutations } from "vuex" 504 - @@ -325,11 +287,9 @@ export default { 436 + @@ -336,11 +298,9 @@ export default { 505 437 NewEvent, 506 438 NewDialog, 507 439 LandingPageHeader, ··· 514 446 PronunciationMenu, 515 447 AuthUserMenu, 516 448 diff --git a/frontend/src/views/SignIn.vue b/frontend/src/views/SignIn.vue 517 - index d667be55..6be32f29 100644 449 + index 95e697fe..67f14bc1 100644 518 450 --- a/frontend/src/views/SignIn.vue 519 451 +++ b/frontend/src/views/SignIn.vue 520 452 @@ -32,70 +32,20 @@ ··· 596 528 </div> 597 529 </div> 598 530 <div class="tw-text-center tw-text-xs"> 599 - @@ -251,7 +201,7 @@ 531 + @@ -250,8 +200,8 @@ 532 + </template> 600 533 601 534 <script> 602 - import { authTypes, calendarTypes } from "@/constants" 535 + -import { calendarTypes } from "@/constants" 603 536 -import { post, signInGoogle, signInOutlook } from "@/utils" 537 + +import { authTypes, calendarTypes } from "@/constants" 604 538 +import { getEventsCreated, post, signInGoogle, signInOutlook } from "@/utils" 605 539 import { mapMutations } from "vuex" 606 540 import Logo from "@/components/Logo.vue" 607 541 608 - @@ -299,6 +249,15 @@ export default { 542 + @@ -293,6 +243,15 @@ export default { 609 543 610 544 methods: { 611 545 ...mapMutations(["setAuthUser"]), ··· 619 553 + window.location.href = `/api/auth/github?${params}` 620 554 + }, 621 555 signIn(provider) { 622 - const state = this.upgradeRedirect 623 - ? { type: authTypes.UPGRADE, upgradeParams: this.$route.query.upgradeParams } 556 + if (provider === calendarTypes.GOOGLE) { 557 + signInGoogle({ state: null, selectAccount: true }) 624 558 diff --git a/server/main.go b/server/main.go 625 - index b038861e..1678b7df 100644 559 + index 16b59b40..85b296ae 100644 626 560 --- a/server/main.go 627 561 +++ b/server/main.go 628 - @@ -175,7 +175,9 @@ func main() { 562 + @@ -172,7 +172,9 @@ func main() { 629 563 router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler)) 630 564 631 565 // Run server
+71
pkgs/timeful/update.sh
··· 1 + #!/usr/bin/env bash 2 + set -euo pipefail 3 + 4 + # Three hashes move together (src, frontend npmDepsHash, server vendorHash), 5 + # and src is wrapped in applyPatches, so nix-update can't do this one. 6 + owner=schej-it 7 + repo=timeful.app 8 + branch=main 9 + attr=timeful 10 + 11 + # Run by update-pkgs from the flake root ($0 is a read-only store copy). 12 + pkg=pkgs/timeful/package.nix 13 + # Linux-only package: always evaluate/build the x86_64-linux variant. 14 + build_attr=".#packages.x86_64-linux.$attr" 15 + 16 + new_rev=$(curl -fsSL "https://api.github.com/repos/$owner/$repo/commits/$branch" | jq -r .sha) 17 + old_rev=$(sed -n 's|^ *rev = "\([^"]*\)";|\1|p' "$pkg") 18 + if [ "$old_rev" = "$new_rev" ]; then 19 + echo "$attr: already up to date ($new_rev)" 20 + exit 0 21 + fi 22 + 23 + new_date=$(curl -fsSL "https://api.github.com/repos/$owner/$repo/commits/$new_rev" | jq -r .commit.committer.date | cut -d T -f1) 24 + new_version="0-unstable-$new_date" 25 + 26 + # Portable in-place sed (BSD and GNU). 27 + edit() { 28 + local tmp 29 + tmp=$(mktemp) 30 + sed "$@" "$pkg" >"$tmp" 31 + cat "$tmp" >"$pkg" 32 + rm -f "$tmp" 33 + } 34 + 35 + # Bump rev + version and blank every hash: with the old ones in place nix 36 + # reuses the cached FODs and the build "succeeds" on stale code. 37 + edit \ 38 + -e "s|^\( *\)rev = \".*\";|\1rev = \"$new_rev\";|" \ 39 + -e "s|^\( *\)version = \".*\";|\1version = \"$new_version\";|" \ 40 + -e "s|^\( *\)hash = \".*\";|\1hash = \"\";|" \ 41 + -e "s|^\( *\)npmDepsHash = \".*\";|\1npmDepsHash = \"\";|" \ 42 + -e "s|^\( *\)vendorHash = \".*\";|\1vendorHash = \"\";|" 43 + 44 + update_hash() { 45 + local field=$1 new=$2 46 + edit -e "s|^\( *\)$field = \"[^\"]*\";|\1$field = \"$new\";|" 47 + } 48 + 49 + # The probe build is *expected* to fail (hash mismatch); `|| true` keeps 50 + # pipefail/errexit from turning that into an empty `got`. awk must not 51 + # `exit` early or tee dies of SIGPIPE and takes the pipeline with it. 52 + probe_hash() { 53 + { nix build --no-link "$build_attr$1" 2>&1 || true; } | tee /dev/stderr \ 54 + | awk '/got:/ && !seen { print $2; seen = 1 }' || true 55 + } 56 + 57 + # Probe each FOD on its own so a hash is never attributed to the wrong field. 58 + for spec in hash:.src npmDepsHash:.frontend.npmDeps vendorHash:.goModules; do 59 + field=${spec%%:*} 60 + sub=${spec#*:} 61 + got=$(probe_hash "$sub") 62 + if [ -z "$got" ]; then 63 + echo "$attr: no hash reported for $field" >&2 64 + exit 1 65 + fi 66 + update_hash "$field" "$got" 67 + echo "$attr: $field → $got" 68 + done 69 + 70 + echo "$attr: updated to $new_rev ($new_version)" 71 + nix build --no-link "$build_attr"