|
152 | 152 | let neIntention = $state(""); |
153 | 153 | let neSetting = $state(""); |
154 | 154 | let neStart = $state(""); |
| 155 | + // "This already happened" — when set, the session is created already ended |
| 156 | + // (with `neEnd`) so it reads as history rather than an ongoing session. |
| 157 | + let nePast = $state(false); |
| 158 | + let neEnd = $state(""); |
155 | 159 | let showNewExp = $state(false); |
156 | 160 | // plain note (kind: "note") — title, body, date; nothing else |
157 | 161 | let nnTitle = $state(""); |
|
676 | 680 | usage = await usageBySubstance(); |
677 | 681 | } |
678 | 682 |
|
| 683 | + /** What a fresh dose-time field should default to. For a session that already |
| 684 | + * ended (a past trip being written up), new doses belong to when it happened, |
| 685 | + * not now — so seed from its start. Live sessions and notes default to now. */ |
| 686 | + function defaultDoseTime(): string { |
| 687 | + return selected?.kind === "session" && selected.ended_at |
| 688 | + ? isoToLocalInput(selected.started_at) |
| 689 | + : nowLocalInput(); |
| 690 | + } |
| 691 | +
|
679 | 692 | async function openExperience(id: number) { |
680 | 693 | lastWarnings = []; |
681 | 694 | editExp = false; |
682 | 695 | editingDoseId = null; |
683 | 696 | dRef = null; |
684 | 697 | exportErr = exportMsg = null; |
685 | 698 | selected = await getExperience(id); |
686 | | - dTime = nowLocalInput(); |
| 699 | + dTime = defaultDoseTime(); |
687 | 700 | } |
688 | 701 |
|
689 | 702 | function openNewExp() { |
|
692 | 705 | if (showNewExp && !neStart) neStart = nowLocalInput(); |
693 | 706 | } |
694 | 707 |
|
| 708 | + // When "this already happened" is ticked, seed the end time from the start so |
| 709 | + // the field isn't empty — the person adjusts both to the real dates. |
| 710 | + function onPastToggle() { |
| 711 | + if (nePast && !neEnd) neEnd = neStart; |
| 712 | + } |
| 713 | +
|
695 | 714 | function openNewNote() { |
696 | 715 | showNewNote = !showNewNote; |
697 | 716 | if (showNewNote) showNewExp = false; |
|
792 | 811 | } |
793 | 812 |
|
794 | 813 | async function submitNewExperience() { |
| 814 | + const startIso = localInputToIso(neStart); |
795 | 815 | const e = await createExperience({ |
796 | 816 | title: neTitle || "Untitled experience", |
797 | 817 | intention: neIntention, |
798 | 818 | setting: neSetting, |
799 | | - started_at: localInputToIso(neStart), |
| 819 | + started_at: startIso, |
800 | 820 | }); |
801 | | - neTitle = neIntention = neSetting = neStart = ""; |
| 821 | + // A past experience is over the moment it's logged — mark it ended so it's |
| 822 | + // history, not a live session (no "ongoing", no live-session controls). If |
| 823 | + // the end time was left blank, fall back to the start rather than to now. |
| 824 | + if (nePast) { |
| 825 | + await endExperience(e.id, neEnd ? localInputToIso(neEnd) : startIso, null, ""); |
| 826 | + } |
| 827 | + neTitle = neIntention = neSetting = neStart = neEnd = ""; |
| 828 | + nePast = false; |
802 | 829 | showNewExp = false; |
803 | 830 | await loadJournal(); |
804 | 831 | await openExperience(e.id); |
|
816 | 843 | }); |
817 | 844 | lastWarnings = res.warnings; |
818 | 845 | dSubstance = dAmount = ""; |
819 | | - dTime = nowLocalInput(); |
| 846 | + dTime = defaultDoseTime(); |
820 | 847 | await openExperienceKeepWarnings(selected.id); |
821 | 848 | await loadJournal(); |
822 | 849 | } |
|
1076 | 1103 | let fbSummary = $state(""); |
1077 | 1104 | let fbDetail = $state(""); |
1078 | 1105 |
|
| 1106 | + // A quick way in from the header, so reporting a bug isn't buried in Settings. |
| 1107 | + let showFeedback = $state(false); |
| 1108 | + function openBugReport() { |
| 1109 | + fbKind = "bug"; |
| 1110 | + showFeedback = true; |
| 1111 | + } |
| 1112 | + // From the modal: open the prefilled issue, then close. (The Settings card |
| 1113 | + // calls openFeedback directly and stays put.) |
| 1114 | + async function sendFeedback() { |
| 1115 | + await openFeedback(); |
| 1116 | + showFeedback = false; |
| 1117 | + } |
| 1118 | +
|
1079 | 1119 | async function openFeedback() { |
1080 | 1120 | const isBug = fbKind === "bug"; |
1081 | 1121 | let version = ""; |
|
1623 | 1663 | <button class:active={tab === "substances"} onclick={() => goTab("substances")}>Substances</button> |
1624 | 1664 | <button class:active={tab === "bysub"} onclick={() => goTab("bysub")}>Substance Log</button> |
1625 | 1665 | <button class:active={tab === "data"} onclick={() => goTab("data")}>Settings</button> |
| 1666 | + <button title="Report a bug or request a feature" onclick={openBugReport}>🐛 Report a bug</button> |
1626 | 1667 | <button title="Emergency & support resources" onclick={openHelp}>Emergency Resources</button> |
1627 | 1668 | </nav> |
1628 | 1669 | </header> |
|
1819 | 1860 | {#if showNewExp} |
1820 | 1861 | <div class="new-exp"> |
1821 | 1862 | <input placeholder="Title" bind:value={neTitle} /> |
1822 | | - <input type="datetime-local" bind:value={neStart} title="Start time" /> |
| 1863 | + <input type="datetime-local" bind:value={neStart} title={nePast ? "When it started" : "Start time"} /> |
| 1864 | + {#if nePast} |
| 1865 | + <input type="datetime-local" bind:value={neEnd} title="When it ended (optional)" /> |
| 1866 | + {/if} |
1823 | 1867 | <input placeholder="Intention (optional)" bind:value={neIntention} /> |
1824 | 1868 | <input placeholder="Set & setting (optional)" bind:value={neSetting} /> |
1825 | | - <button class="primary small-btn" onclick={submitNewExperience}>Start</button> |
| 1869 | + <button class="primary small-btn" onclick={submitNewExperience}>{nePast ? "Log it" : "Start"}</button> |
1826 | 1870 | </div> |
| 1871 | + <label class="past-toggle"> |
| 1872 | + <input type="checkbox" bind:checked={nePast} onchange={onPastToggle} /> |
| 1873 | + This already happened — I'm logging a past experience |
| 1874 | + </label> |
1827 | 1875 | {/if} |
1828 | 1876 |
|
1829 | 1877 | {#if showNewNote} |
@@ -2663,6 +2711,39 @@ Peak was intense and connected; gentle comedown by 1am. Drank lots of water, no |
2663 | 2711 | </div> |
2664 | 2712 | {/if} |
2665 | 2713 |
|
| 2714 | + {#if showFeedback} |
| 2715 | + <!-- svelte-ignore a11y_click_events_have_key_events --> |
| 2716 | + <div class="modal-overlay" role="presentation" onclick={() => (showFeedback = false)}> |
| 2717 | + <!-- svelte-ignore a11y_click_events_have_key_events --> |
| 2718 | + <div class="help-modal" role="dialog" aria-modal="true" tabindex="-1" onclick={(e) => e.stopPropagation()}> |
| 2719 | + <h2>{fbKind === "bug" ? "Report a bug" : "Request a feature"}</h2> |
| 2720 | + <p class="muted small"> |
| 2721 | + Opens a prefilled issue on GitHub in your browser — nothing leaves your journal; you |
| 2722 | + write and send it there. |
| 2723 | + </p> |
| 2724 | + <div class="fb-kind"> |
| 2725 | + <label><input type="radio" bind:group={fbKind} value="bug" /> Report a bug</label> |
| 2726 | + <label><input type="radio" bind:group={fbKind} value="feature" /> Request a feature</label> |
| 2727 | + </div> |
| 2728 | + <input |
| 2729 | + class="fb-field" |
| 2730 | + placeholder={fbKind === "bug" ? "What went wrong? (short summary)" : "What would you like? (short summary)"} |
| 2731 | + bind:value={fbSummary} |
| 2732 | + /> |
| 2733 | + <textarea |
| 2734 | + class="fb-field" |
| 2735 | + rows="3" |
| 2736 | + placeholder="Any detail you want to include (optional — you can also write it on GitHub)" |
| 2737 | + bind:value={fbDetail} |
| 2738 | + ></textarea> |
| 2739 | + <div class="row-actions"> |
| 2740 | + <button class="ghost small-btn" onclick={() => (showFeedback = false)}>Cancel</button> |
| 2741 | + <button class="primary small-btn" onclick={sendFeedback}>Continue on GitHub →</button> |
| 2742 | + </div> |
| 2743 | + </div> |
| 2744 | + </div> |
| 2745 | + {/if} |
| 2746 | + |
2666 | 2747 | <!-- ============ LIVE SESSION ============ --> |
2667 | 2748 | {#if liveSession && selected} |
2668 | 2749 | <div class="live"> |
@@ -2821,6 +2902,8 @@ Peak was intense and connected; gentle comedown by 1am. Drank lots of water, no |
2821 | 2902 |
|
2822 | 2903 | .new-exp, .dose-form, .new-sub { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.8rem 0; align-items: center; } |
2823 | 2904 | .new-exp input, .new-sub input { flex: 1; min-width: 8rem; } |
| 2905 | + .past-toggle { display: flex; align-items: center; gap: 0.45rem; margin: -0.3rem 0 0.8rem; color: var(--muted); font-size: 0.85rem; cursor: pointer; } |
| 2906 | + .past-toggle input { cursor: pointer; } |
2824 | 2907 | .new-note { display: flex; flex-direction: column; gap: 0.5rem; margin: 0.8rem 0; } |
2825 | 2908 | .new-note textarea { width: 100%; resize: vertical; } |
2826 | 2909 | .note-pill { font-style: italic; } |
|
0 commit comments