diff --git a/.run/Dev Desktop.run.xml b/.run/Dev Desktop.run.xml
index 4ebc595c..5fbed34e 100644
--- a/.run/Dev Desktop.run.xml
+++ b/.run/Dev Desktop.run.xml
@@ -7,8 +7,8 @@
-
+
-
\ No newline at end of file
+
diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png
index 91eb085b..90b6acd0 100644
Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ
diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png
index a7181475..4e534b32 100644
Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ
diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png
index 349717c8..179779b3 100644
Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ
diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png
index 38a8f582..706f5fcc 100644
Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ
diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png
index 0c600807..e8126a6b 100644
Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ
diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png
index 677b2fc7..0b4e7bba 100644
Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ
diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png
index 52442448..e2c53f7f 100644
Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ
diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png
index 7a0a1ea1..1b05c23c 100644
Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ
diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png
index 071db292..46691e23 100644
Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ
diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png
index ac214202..b939e4aa 100644
Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ
diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png
index 2ab8447c..29be51b3 100644
Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ
diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png
index 1b4ebe32..c88253a3 100644
Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ
diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png
index 6046af1f..13683473 100644
Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ
diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns
index cb6a4161..2c7bea99 100644
Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ
diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico
index e94bcf6b..ccd1ad36 100644
Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ
diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png
index 0eeac1eb..c60732b5 100644
Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ
diff --git a/src-tauri/tauri.toml b/src-tauri/tauri.toml
index 39e5d3d9..616e6268 100644
--- a/src-tauri/tauri.toml
+++ b/src-tauri/tauri.toml
@@ -6,7 +6,7 @@ distDir = "../dist"
withGlobalTauri = false
[package]
-productName = "Twosomnia"
+productName = "Yaak"
version = "0.0.1"
[tauri.allowlist]
@@ -34,7 +34,7 @@ icon = [
"icons/icon.icns",
"icons/icon.ico"
]
-identifier = "co.schier.twosomnia"
+identifier = "co.schier.yaak"
longDescription = ""
resources = [ "plugins/*", "migrations/*" ]
shortDescription = ""
@@ -63,7 +63,7 @@ dialog = true
fullscreen = false
height = 800
resizable = true
-title = "Twosomnia"
+title = "Yaak"
width = 1_400
titleBarStyle = "Overlay"
hiddenTitle = true
diff --git a/src-web/components/Editor/Editor.css b/src-web/components/Editor/Editor.css
index 00dd6a95..f2d9f684 100644
--- a/src-web/components/Editor/Editor.css
+++ b/src-web/components/Editor/Editor.css
@@ -27,9 +27,10 @@
.cm-editor .placeholder-widget {
background-color: hsl(var(--color-blue-400));
+ text-shadow: 0 0 0.2em black;
padding: 0.05em 0.3em;
border-radius: 0.2em;
- color: white;
+ color: hsl(var(--color-blue-900));
cursor: pointer;
}
@@ -55,15 +56,13 @@
padding-right: 1.5em;
}
-.cm-singleline .cm-scroller {
- display: flex;
+.cm-singleline .cm-editor .cm-scroller {
+ @apply flex;
align-items: center !important;
}
.cm-editor .cm-gutters {
- background-color: hsl(var(--color-gray-50));
- border-right: 0;
- color: hsl(var(--color-gray-200));
+ @apply bg-gray-50 border-r-0 text-gray-200;
}
.cm-editor .cm-gutterElement {
@@ -71,83 +70,92 @@
}
.cm-editor .fold-gutter-icon {
- height: 1.5em;
- padding-top: 0.2em;
- padding-left: 0.4em;
- padding-right: 0.4em;
- cursor: pointer;
- border-radius: 0.2em;
+ @apply pt-[0.3em] pl-[0.4em] px-[0.4em] h-4 cursor-pointer rounded;
}
.cm-editor .fold-gutter-icon::after {
- display: block;
- width: 0.5em;
- height: 0.5em;
- border: 1px solid transparent;
- border-left-color: currentColor;
- border-bottom-color: currentColor;
- transform: rotate(-45deg);
- content: "";
+ @apply block w-1.5 h-1.5 border-transparent -rotate-45
+ border-l border-b border-l-[currentColor] border-b-[currentColor] content-[''];
}
.cm-editor .fold-gutter-icon[data-open] {
- padding-top: 0.4em;
- padding-left: 0.2em;
+ @apply pt-[0.4em] pl-[0.3em];
}
.cm-editor .fold-gutter-icon[data-open]::after {
- transform: rotate(-135deg);
+ @apply rotate-[-135deg];
}
.cm-editor .fold-gutter-icon:hover {
- background-color: hsl(var(--color-gray-100)/0.2);
- color: hsl(var(--color-gray-400));
+ @apply text-gray-400 bg-gray-100/20;
}
.cm-editor.cm-focused .cm-gutters {
- color: hsl(var(--color-gray-300));
+ @apply text-gray-300;
}
.cm-editor .cm-foldPlaceholder {
- background-color: hsl(var(--color-gray-100));
- border: 1px solid hsl(var(--color-gray-200));
- padding: 0 0.3em;
+ @apply px-2 border border-gray-200 bg-gray-100;
}
.cm-editor .cm-activeLineGutter,
.cm-editor .cm-activeLine {
- background-color: transparent;
+ @apply bg-transparent;
}
.cm-editor.cm-focused .cm-activeLineGutter {
- color: hsl(var(--color-gray-800));
+ @apply text-gray-800;
}
.cm-editor * {
- cursor: text;
+ @apply cursor-text;
}
.cm-editor .cm-cursor {
- border-left: 2px solid hsl(var(--color-gray-900));
+ @apply border-l-2 border-gray-800;
}
.cm-editor .cm-selectionBackground {
- background-color: hsl(var(--color-gray-200));
+ @apply bg-gray-200;
}
.cm-editor.cm-focused .cm-selectionBackground {
- background-color: hsl(var(--color-gray-200));
+ @apply bg-gray-200;
}
/* --> Add padding to container. For some reason, using padding on both adds an extra
* 1px offset so we need to use a combination of padding and margin.
*/
.cm-editor .cm-gutters {
- padding-top: 0.2em;
+ @apply pt-1;
}
.cm-editor .cm-content {
- margin-top: 0.2em;
+ @apply mt-1;
}
/* <-- */
+
+.cm-editor .cm-tooltip {
+ @apply shadow-lg border-0 bg-background rounded overflow-hidden text-gray-900;
+}
+
+.cm-editor .cm-tooltip * {
+ @apply transition-none;
+}
+
+.cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul {
+ @apply p-1 max-h-[40vh];
+}
+
+.cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li {
+ @apply cursor-default py-1 px-2 rounded-sm text-gray-500;
+}
+
+.cm-editor .cm-tooltip.cm-tooltip-autocomplete > ul > li[aria-selected] {
+ @apply bg-gray-50 text-gray-800;
+}
+
+.cm-editor .cm-tooltip.cm-tooltip-autocomplete .cm-completionIcon {
+ @apply text-sm;
+}
diff --git a/src-web/components/Editor/Editor.tsx b/src-web/components/Editor/Editor.tsx
index 33cc0e95..b57a0119 100644
--- a/src-web/components/Editor/Editor.tsx
+++ b/src-web/components/Editor/Editor.tsx
@@ -1,12 +1,13 @@
-import type { Transaction, TransactionSpec } from '@codemirror/state';
-import { Compartment, EditorSelection, EditorState, Prec } from '@codemirror/state';
-import { placeholder as placeholderExt } from '@codemirror/view';
+import { defaultKeymap } from '@codemirror/commands';
+import { Compartment, EditorState, Prec } from '@codemirror/state';
+import { keymap, placeholder as placeholderExt } from '@codemirror/view';
import classnames from 'classnames';
import { EditorView } from 'codemirror';
import type { HTMLAttributes } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import './Editor.css';
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
+import { singleLineExt } from './singleLine';
interface Props extends Omit, 'onChange'> {
contentType: string;
@@ -106,43 +107,25 @@ function getExtensions({
>) {
const ext = getLanguageExtension({ contentType, useTemplating });
return [
- ...(singleLine
- ? [
- Prec.high(
- EditorView.domEventHandlers({
- keydown: (e) => {
- // TODO: Figure out how to not have this not trigger on autocomplete selection
- if (e.key === 'Enter') {
- e.preventDefault();
- onSubmit?.();
- }
- },
- }),
- ),
- EditorState.transactionFilter.of(
- (tr: Transaction): TransactionSpec | TransactionSpec[] => {
- if (!tr.isUserEvent('input.paste')) return tr;
-
- const trs: TransactionSpec[] = [];
- tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
- let insert = '';
- for (const line of inserted) {
- insert += line.replace('\n', '');
- }
- const changes = [{ from: fromB, to: toA, insert }];
- // Update selection now that the text has been changed
- const selection = EditorSelection.create([EditorSelection.cursor(toB - 1)], 0);
- trs.push({ ...tr, selection, changes });
- });
- return trs;
- },
- ),
- ]
- : []),
...baseExtensions,
+ keymap.of(singleLine ? defaultKeymap.filter((k) => k.key !== 'Enter') : defaultKeymap),
+ ...(singleLine ? [singleLineExt()] : []),
...(!singleLine ? [multiLineExtensions] : []),
...(ext ? [ext] : []),
...(placeholder ? [placeholderExt(placeholder)] : []),
+
+ // Handle onSubmit
+ ...(onSubmit
+ ? [
+ EditorView.domEventHandlers({
+ keydown: (e) => {
+ console.log('KEYDOWN', e);
+ if (e.key === 'Enter') onSubmit?.();
+ },
+ }),
+ ]
+ : []),
+ // Handle onChange
EditorView.updateListener.of((update) => {
if (typeof onChange === 'function' && update.docChanged) {
onChange(update.state.doc.toString());
diff --git a/src-web/components/Editor/completion/completion.ts b/src-web/components/Editor/completion/completion.ts
index 32c4cd1e..b073d9b3 100644
--- a/src-web/components/Editor/completion/completion.ts
+++ b/src-web/components/Editor/completion/completion.ts
@@ -8,25 +8,30 @@ const variables = [
{ name: 'BASE_URL' },
{ name: 'TOKEN' },
{ name: 'PROJECT_ID' },
+ { name: 'DUMMY' },
+ { name: 'DUMMY_2' },
];
export function myCompletions(context: CompletionContext) {
- const toStartOfName = context.matchBefore(/\w*/);
- const toStartOfVariable = context.matchBefore(/\$\{.*/);
+ const toStartOfName = context.explicit ? context.matchBefore(/\w*/) : context.matchBefore(/\w+/);
+ const toStartOfVariable = context.matchBefore(/\$\{?\[?\s*\w*/);
const toMatch = toStartOfVariable ?? toStartOfName ?? null;
if (toMatch === null) {
return null;
}
- if (toMatch.from === toMatch.to && !context.explicit) {
+ // Match a minimum of two characters when typing a variable ${[...]} to prevent it
+ // from opening on "$"
+ if (toStartOfVariable !== null && toMatch.to - toMatch.from < 2 && !context.explicit) {
return null;
}
return {
from: toMatch.from,
options: variables.map((v) => ({
- label: `${openTag}${v.name}${closeTag}`,
+ label: toStartOfVariable ? `${openTag}${v.name}${closeTag}` : v.name,
+ apply: `${openTag}${v.name}${closeTag}`,
type: 'variable',
})),
};
diff --git a/src-web/components/Editor/extensions.ts b/src-web/components/Editor/extensions.ts
index bd25dcba..43fa55a5 100644
--- a/src-web/components/Editor/extensions.ts
+++ b/src-web/components/Editor/extensions.ts
@@ -102,13 +102,12 @@ export function getLanguageExtension({
}
export const baseExtensions = [
- keymap.of([...defaultKeymap]),
highlightSpecialChars(),
history(),
drawSelection(),
dropCursor(),
bracketMatching(),
- autocompletion(),
+ autocompletion({ activateOnTyping: false, closeOnBlur: true }),
syntaxHighlighting(myHighlightStyle),
EditorState.allowMultipleSelections.of(true),
];
diff --git a/src-web/components/Editor/singleLine.ts b/src-web/components/Editor/singleLine.ts
new file mode 100644
index 00000000..1a166b59
--- /dev/null
+++ b/src-web/components/Editor/singleLine.ts
@@ -0,0 +1,29 @@
+import type { Transaction, TransactionSpec } from '@codemirror/state';
+import { EditorSelection, EditorState } from '@codemirror/state';
+
+export function singleLineExt() {
+ return EditorState.transactionFilter.of(
+ (tr: Transaction): TransactionSpec | TransactionSpec[] => {
+ if (!tr.isUserEvent('input')) return tr;
+
+ const trs: TransactionSpec[] = [];
+ tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
+ let insert = '';
+ let newlinesRemoved = 0;
+ for (const line of inserted) {
+ const newLine = line.replace('\n', '');
+ newlinesRemoved += line.length - newLine.length;
+ insert += newLine;
+ }
+
+ // Update cursor position based on how many newlines were removed
+ const cursor = EditorSelection.cursor(toB - newlinesRemoved);
+ const selection = EditorSelection.create([cursor], 0);
+
+ const changes = [{ from: fromB, to: toA, insert }];
+ trs.push({ ...tr, selection, changes });
+ });
+ return trs;
+ },
+ );
+}
diff --git a/src-web/components/Editor/twig/extension.ts b/src-web/components/Editor/twig/extension.ts
index 43cac83e..b0d226f1 100644
--- a/src-web/components/Editor/twig/extension.ts
+++ b/src-web/components/Editor/twig/extension.ts
@@ -1,16 +1,15 @@
-import { LRLanguage, LanguageSupport } from '@codemirror/language';
+import { LanguageSupport, LRLanguage } from '@codemirror/language';
import { parseMixed } from '@lezer/common';
import { myCompletions } from '../completion/completion';
import { placeholders } from '../widgets';
import { parser as twigParser } from './twig';
export function twig(base?: LanguageSupport) {
- const parser = mixedOrPlainParser(base);
- const twigLanguage = LRLanguage.define({ name: 'twig', parser, languageData: {} });
- const completion = twigLanguage.data.of({
+ const language = mixedOrPlainLanguage(base);
+ const completion = language.data.of({
autocomplete: myCompletions,
});
- const languageSupport = new LanguageSupport(twigLanguage, [completion]);
+ const languageSupport = new LanguageSupport(language, [completion]);
if (base) {
const completion2 = base.language.data.of({ autocomplete: myCompletions });
@@ -21,31 +20,22 @@ export function twig(base?: LanguageSupport) {
}
}
-function mixedOrPlainParser(base?: LanguageSupport) {
- if (base === undefined) {
- return twigParser;
+function mixedOrPlainLanguage(base?: LanguageSupport): LRLanguage {
+ const name = 'twig';
+
+ if (base == null) {
+ return LRLanguage.define({ name, parser: twigParser });
}
- const mixedParser = twigParser.configure({
- props: [
- // Add basic folding/indent metadata
- // foldNodeProp.add({ Conditional: foldInside }),
- // indentNodeProp.add({
- // Conditional: (cx) => {
- // const closed = /^\s*\{% endif/.test(cx.textAfter);
- // return cx.lineIndent(cx.node.from) + (closed ? 0 : cx.unit);
- // },
- // }),
- ],
+ const parser = twigParser.configure({
wrap: parseMixed((node) => {
- return node.type.isTop
- ? {
- parser: base.language.parser,
- overlay: (node) => node.type.name === 'Text',
- }
- : null;
+ if (!node.type.isTop) return null;
+ return {
+ parser: base.language.parser,
+ overlay: (node) => node.type.name === 'Text',
+ };
}),
});
- return mixedParser;
+ return LRLanguage.define({ name, parser });
}
diff --git a/src-web/components/Editor/twig/twig.grammar b/src-web/components/Editor/twig/twig.grammar
index 60b1591d..11324af9 100644
--- a/src-web/components/Editor/twig/twig.grammar
+++ b/src-web/components/Editor/twig/twig.grammar
@@ -9,9 +9,9 @@ directive {
}
@tokens {
- Text { ![${[] Text? }
+ Text { ![$] Text? }
space { @whitespace+ }
- DirectiveContent { ![\]}$] DirectiveContent? }
+ DirectiveContent { ![\]}] DirectiveContent? }
@precedence { space DirectiveContent }
"${[" "]}"
}
diff --git a/src-web/components/Editor/twig/twig.ts b/src-web/components/Editor/twig/twig.ts
index 3057bdaa..a806f562 100644
--- a/src-web/components/Editor/twig/twig.ts
+++ b/src-web/components/Editor/twig/twig.ts
@@ -11,7 +11,7 @@ export const parser = LRParser.deserialize({
propSources: [highlight],
skippedNodes: [0],
repeatNodeCount: 1,
- tokenData: ")a~RqOX#YX^%i^p#Ypq%iqt#Ytu'vu!}#Y!}#O$V#O#P#Y#P#Q(X#Q#o#Y#o#p$V#p#q#Y#q#r$t#r#y#Y#y#z%i#z$f#Y$f$g%i$g#BY#Y#BY#BZ%i#BZ$IS#Y$IS$I_%i$I_$I|#Y$I|$JO%i$JO$JT#Y$JT$JU%i$JU$KV#Y$KV$KW%i$KW&FU#Y&FU&FV%i&FV;'S#Y;'S;=`%c<%lO#YR#a[UPSQOt#Yu!}#Y!}#O$V#O#P#Y#P#Q$t#Q#o#Y#o#p$V#p#q#Y#q#r$t#r;'S#Y;'S;=`%c<%lO#YQ$[USQOt$Vu#P$V#Q#q$V#r;'S$V;'S;=`$n<%lO$VQ$qP;=`<%l$VP$yUUPOt$tu!}$t#O#o$t#p;'S$t;'S;=`%]<%lO$tP%`P;=`<%l$tR%fP;=`<%l#YR%rpUPYQSQOX#YX^%i^p#Ypq%iqt#Yu!}#Y!}#O$V#O#P#Y#P#Q$t#Q#o#Y#o#p$V#p#q#Y#q#r$t#r#y#Y#y#z%i#z$f#Y$f$g%i$g#BY#Y#BY#BZ%i#BZ$IS#Y$IS$I_%i$I_$I|#Y$I|$JO%i$JO$JT#Y$JT$JU%i$JU$KV#Y$KV$KW%i$KW&FU#Y&FU&FV%i&FV;'S#Y;'S;=`%c<%lO#Y~'yP#o#p'|~(PP!}#O(S~(XOR~R(^WUPOt$tu!}$t#O#o$t#p#q$t#q#r(v#r;'S$t;'S;=`%]<%lO$tR(}UTQUPOt$tu!}$t#O#o$t#p;'S$t;'S;=`%]<%lO$t",
+ tokenData: ")gRRmOX!|X^$y^p!|pq$yqt!|tu&}u#P!|#P#Q(k#Q#q!|#q#r$[#r#y!|#y#z$y#z$f!|$f$g$y$g#BY!|#BY#BZ$y#BZ$IS!|$IS$I_$y$I_$I|!|$I|$JO$y$JO$JT!|$JT$JU$y$JU$KV!|$KV$KW$y$KW&FU!|&FU&FV$y&FV;'S!|;'S;=`$s<%lO!|R#TXUPSQOt!|tu#pu#P!|#P#Q$[#Q#q!|#q#r$[#r;'S!|;'S;=`$s<%lO!|Q#uTSQO#P#p#Q#q#p#r;'S#p;'S;=`$U<%lO#pQ$XP;=`<%l#pP$aSUPOt$[u;'S$[;'S;=`$m<%lO$[P$pP;=`<%l$[R$vP;=`<%l!|R%SmUPYQSQOX!|X^$y^p!|pq$yqt!|tu#pu#P!|#P#Q$[#Q#q!|#q#r$[#r#y!|#y#z$y#z$f!|$f$g$y$g#BY!|#BY#BZ$y#BZ$IS!|$IS$I_$y$I_$I|!|$I|$JO$y$JO$JT!|$JT$JU$y$JU$KV!|$KV$KW$y$KW&FU!|&FU&FV$y&FV;'S!|;'S;=`$s<%lO!|R'SVSQO#P#p#Q#o#p#o#p'i#p#q#p#r;'S#p;'S;=`$U<%lO#pR'nVSQO!}#p!}#O(T#O#P#p#Q#q#p#r;'S#p;'S;=`$U<%lO#pR([TRPSQO#P#p#Q#q#p#r;'S#p;'S;=`$U<%lO#pR(pUUPOt$[u#q$[#q#r)S#r;'S$[;'S;=`$m<%lO$[R)ZSTQUPOt$[u;'S$[;'S;=`$m<%lO$[",
tokenizers: [0, 1],
topRules: {"Template":[0,1]},
tokenPrec: 25
diff --git a/src-web/components/ResponsePane.tsx b/src-web/components/ResponsePane.tsx
index 524a70c9..210427e0 100644
--- a/src-web/components/ResponsePane.tsx
+++ b/src-web/components/ResponsePane.tsx
@@ -60,7 +60,7 @@ export function ResponsePane({ requestId, error }: Props) {
},
'-----',
...responses.data.slice(0, 10).map((r) => ({
- label: r.status + ' - ' + r.elapsed,
+ label: r.status + ' - ' + r.elapsed + ' ms',
leftSlot: response?.id === r.id ? : <>>,
onSelect: () => setActiveResponseId(r.id),
})),