diff --git a/.github/workflows/preview-cleanup.yml b/.github/workflows/preview-cleanup.yml index 60f21c8d9..2df3d98d9 100644 --- a/.github/workflows/preview-cleanup.yml +++ b/.github/workflows/preview-cleanup.yml @@ -54,7 +54,7 @@ jobs: wrangler delete --name "$WORKER_NAME" --force || echo "Worker may not exist" - name: Delete GitHub Deployment - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const environment = `preview-pr-${{ github.event.pull_request.number }}`; diff --git a/.github/workflows/preview-deploy.yml b/.github/workflows/preview-deploy.yml index 62d2266af..c26ea1cf0 100644 --- a/.github/workflows/preview-deploy.yml +++ b/.github/workflows/preview-deploy.yml @@ -40,7 +40,7 @@ jobs: - name: Resolve preview request id: preview - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const { resolvePreviewRequest } = require('./trusted-preview-resolver/workers/preview/deploy/resolve_preview_request.cjs'); @@ -66,7 +66,7 @@ jobs: - name: Create GitHub Deployment if: env.IS_FORK == 'false' id: deployment - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const prNumber = process.env.PR_NUMBER; @@ -206,12 +206,14 @@ jobs: cp -R trusted/workers/preview/src "$preview_dir/src" mkdir -p "$preview_dir/deploy" cp trusted/workers/preview/deploy/redact_preview_log.sh "$preview_dir/deploy/redact_preview_log.sh" + cp trusted/workers/preview/deploy/render_preview_config.cjs "$preview_dir/deploy/render_preview_config.cjs" chmod 0755 "$preview_dir/deploy/redact_preview_log.sh" diagnostics_nonce="$(openssl rand -hex 32)" sed -i "s/\${PR_NUMBER}/${PR_NUMBER}/g" "$preview_dir/wrangler.toml" sed -i "s/\${PR_NUMBER}/${PR_NUMBER}/g" "$preview_dir/src/index.ts" sed -i "s/\${PREVIEW_DIAGNOSTICS_NONCE}/${diagnostics_nonce}/g" "$preview_dir/src/index.ts" + cp "$preview_dir/wrangler.toml" "$preview_dir/wrangler.source.toml" if grep -F "\${PREVIEW_DIAGNOSTICS_NONCE}" "$preview_dir/src/index.ts" >/dev/null; then echo "Preview diagnostics nonce placeholder was not replaced" >&2 @@ -248,6 +250,7 @@ jobs: set -euo pipefail cd "$RUNNER_TEMP/sure-preview-worker" + source_config="$RUNNER_TEMP/sure-preview-worker/wrangler.source.toml" config_path="$RUNNER_TEMP/sure-preview-worker/wrangler.toml" image_tag="sure-preview-pr-${PR_NUMBER}:${HEAD_SHA}" temporary_image_ref="registry.cloudflare.com/${CLOUDFLARE_ACCOUNT_ID}/${image_tag}" @@ -257,23 +260,8 @@ jobs: # wrangler containers push validates wrangler.toml, so point the trusted # config at a registry-shaped ref while it pushes the verified local image. - TEMPORARY_IMAGE_REF="$temporary_image_ref" node - "$config_path" <<'NODE' - const fs = require('node:fs'); - - const configPath = process.argv[2]; - const imageRef = process.env.TEMPORARY_IMAGE_REF; - - if (!/^registry\.cloudflare\.com\/[A-Za-z0-9_-]+\/sure-preview-pr-[1-9][0-9]*:[a-f0-9]{40}$/.test(imageRef || '')) { - throw new Error('Expected registry-shaped preview image ref before wrangler containers push'); - } - - const original = fs.readFileSync(configPath, 'utf8'); - const updated = original.replace(/image = "[^"]+"/, `image = ${JSON.stringify(imageRef)}`); - if (updated === original) { - throw new Error('Expected wrangler.toml to contain an image entry to rewrite before push'); - } - fs.writeFileSync(configPath, updated); - NODE + PREVIEW_IMAGE_REF="$temporary_image_ref" node ./deploy/render_preview_config.cjs render "$source_config" "$config_path" + cp "$config_path" "$RUNNER_TEMP/wrangler-push.toml" set +e ./node_modules/.bin/wrangler containers push "$image_tag" 2>&1 | tee "$push_log" | ./deploy/redact_preview_log.sh @@ -285,19 +273,7 @@ jobs: exit "$push_status" fi - image_ref="$(node - "$clean_log" <<'NODE' - const fs = require('node:fs'); - - const logPath = process.argv[2]; - const log = fs.readFileSync(logPath, 'utf8'); - const expectedSuffix = `sure-preview-pr-${process.env.PR_NUMBER}:${process.env.HEAD_SHA}`; - const pattern = /registry\.cloudflare\.com\/[A-Za-z0-9_-]+\/sure-preview-pr-[1-9][0-9]*:[a-f0-9]{40}/g; - const matches = [...log.matchAll(pattern)].map((match) => match[0]); - const imageRef = matches.findLast((candidate) => candidate.endsWith(`/${expectedSuffix}`)); - - if (imageRef) process.stdout.write(imageRef); - NODE - )" + image_ref="$(node ./deploy/render_preview_config.cjs find "$clean_log")" if [ -z "$image_ref" ]; then echo "Could not find Cloudflare registry image reference in wrangler output" >&2 @@ -312,30 +288,12 @@ jobs: run: | set -euo pipefail + source_config="$RUNNER_TEMP/sure-preview-worker/wrangler.source.toml" config_path="$RUNNER_TEMP/sure-preview-worker/wrangler.toml" - # Use Node instead of sed so the replacement preserves TOML string syntax. - node - "$config_path" <<'NODE' - const fs = require('node:fs'); - - const configPath = process.argv[2]; - const imageRef = process.env.IMAGE_REF; - const expectedSuffix = `sure-preview-pr-${process.env.PR_NUMBER}:${process.env.HEAD_SHA}`; - - if (!/^registry\.cloudflare\.com\/[A-Za-z0-9_-]+\/sure-preview-pr-[1-9][0-9]*:[a-f0-9]{40}$/.test(imageRef || '')) { - throw new Error('Expected a Cloudflare registry image reference'); - } - - if (!imageRef.endsWith(`/${expectedSuffix}`)) { - throw new Error('Cloudflare registry image reference does not match this preview artifact'); - } - - const original = fs.readFileSync(configPath, 'utf8'); - const updated = original.replace(/image = "[^"]+"/, `image = ${JSON.stringify(imageRef)}`); - if (updated === original) { - throw new Error('Expected wrangler.toml to contain an image entry to rewrite'); - } - fs.writeFileSync(configPath, updated); - NODE + # Render from the preserved trusted source template so the push-time + # registry ref cannot make the final deploy rewrite stateful. + PREVIEW_IMAGE_REF="$IMAGE_REF" node "$RUNNER_TEMP/sure-preview-worker/deploy/render_preview_config.cjs" render "$source_config" "$config_path" + cp "$config_path" "$RUNNER_TEMP/wrangler-final.toml" # Print a redacted copy for logs without mutating the config used by deploy. redacted_config="$RUNNER_TEMP/wrangler-redacted.toml" @@ -395,27 +353,66 @@ jobs: run: | set -euo pipefail - diagnostics_file="$RUNNER_TEMP/preview-diagnostics.json" + diagnostics_dir="$RUNNER_TEMP/preview-diagnostics" + diagnostics_file="$diagnostics_dir/preview-diagnostics.json" + latest_metrics_file="$diagnostics_dir/latest-metrics.json" + polls_log="$diagnostics_dir/metrics-polls.log" + summary_file="$diagnostics_dir/summary.md" last_error="" + mkdir -p "$diagnostics_dir" for attempt in $(seq 1 40); do if curl -fsS --connect-timeout 5 --max-time 15 "$PREVIEW_URL/_container_status" -o "$diagnostics_file"; then - if jq -e '.previewReady == true or .previewFailed == true' "$diagnostics_file" >/dev/null; then - break + if jq -e . "$diagnostics_file" >/dev/null 2>&1; then + jq -c --argjson attempt "$attempt" --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + '{attempt: $attempt, at: $at, previewReady: (.previewReady // false), previewFailed: (.previewFailed // false), progress: (.progress // {}), timings: (.timings // {})}' \ + "$diagnostics_file" >> "$polls_log" + jq '{previewReady: (.previewReady // false), previewFailed: (.previewFailed // false), progress: (.progress // {}), timings: (.timings // {})}' "$diagnostics_file" > "$latest_metrics_file" + + if jq -e '.previewReady == true or .previewFailed == true' "$diagnostics_file" >/dev/null; then + break + fi + else + last_error="invalid diagnostics JSON on attempt ${attempt}" + raw_snippet="$(head -c 2048 "$diagnostics_file")" + latest_metrics_snapshot="none" + if [ -f "$latest_metrics_file" ]; then + latest_metrics_snapshot="$(head -c 2048 "$latest_metrics_file")" + fi + jq -nc --argjson attempt "$attempt" --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg error "$last_error" --arg latestMetrics "$latest_metrics_snapshot" --arg rawSnippet "$raw_snippet" \ + '{attempt: $attempt, at: $at, error: $error, latestMetrics: $latestMetrics, rawSnippet: $rawSnippet}' >> "$polls_log" fi else last_error="curl failed on attempt ${attempt}" + jq -nc --argjson attempt "$attempt" --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg error "$last_error" \ + '{attempt: $attempt, at: $at, error: $error}' >> "$polls_log" fi sleep 3 done - if [ ! -s "$diagnostics_file" ]; then + if [ ! -s "$diagnostics_file" ] || ! jq -e . "$diagnostics_file" >/dev/null 2>&1; then jq -n --arg error "${last_error:-preview diagnostics unavailable}" \ --arg url "$PREVIEW_URL" \ '{previewReady: false, previewFailed: false, error: $error, previewUrl: $url}' > "$diagnostics_file" fi + jq '{previewReady: (.previewReady // false), previewFailed: (.previewFailed // false), progress: (.progress // {}), timings: (.timings // {}), error: (.error // null)}' "$diagnostics_file" > "$latest_metrics_file" + { + echo "# Preview diagnostics" + echo + echo "- PR: ${PR_NUMBER}" + echo "- Commit: ${HEAD_SHA}" + echo "- Preview URL: ${PREVIEW_URL}" + echo "- Preview ready: $(jq -r '.previewReady // false' "$diagnostics_file")" + echo "- Preview failed: $(jq -r '.previewFailed // false' "$diagnostics_file")" + echo "- Phase: $(jq -r '.progress.phase // "unknown"' "$diagnostics_file")" + echo "- Stage: $(jq -r '.progress.stage // "unknown"' "$diagnostics_file")" + echo "- Seconds to Rails ready: $(jq -r '.timings.secondsToRailsReady // "unknown"' "$diagnostics_file")" + echo "- Seconds to demo data ready: $(jq -r '.timings.secondsToDemoDataReady // "unknown"' "$diagnostics_file")" + echo "- Seconds to preview ready: $(jq -r '.timings.secondsToPreviewReady // "unknown"' "$diagnostics_file")" + } > "$summary_file" + jq -c . "$diagnostics_file" if jq -e '.previewFailed == true' "$diagnostics_file" >/dev/null; then @@ -441,7 +438,7 @@ jobs: uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: preview-diagnostics-pr-${{ env.PR_NUMBER }}-${{ env.HEAD_SHA }} - path: ${{ runner.temp }}/preview-diagnostics.json + path: ${{ runner.temp }}/preview-diagnostics if-no-files-found: error retention-days: 3 @@ -493,6 +490,9 @@ jobs: }' "$manifest_file" > "$diagnostics_dir/preview-image-manifest.json" fi + sanitize_copy "$RUNNER_TEMP/sure-preview-worker/wrangler.source.toml" "$diagnostics_dir/wrangler-source.toml" + sanitize_copy "$RUNNER_TEMP/wrangler-push.toml" "$diagnostics_dir/wrangler-push.toml" + sanitize_copy "$RUNNER_TEMP/wrangler-final.toml" "$diagnostics_dir/wrangler-final.toml" sanitize_copy "$RUNNER_TEMP/sure-preview-worker/wrangler.toml" "$diagnostics_dir/wrangler.toml" sanitize_copy "$RUNNER_TEMP/wrangler-containers-push.clean.log" "$diagnostics_dir/wrangler-containers-push.log" if [ -f "$RUNNER_TEMP/wrangler-deploy.clean.log" ]; then @@ -551,7 +551,7 @@ jobs: steps: - name: Update Deployment Status - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const state = process.env.DEPLOY_RESULT === 'success' ? 'success' : 'failure'; @@ -581,7 +581,7 @@ jobs: steps: - name: Comment on PR - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const previewUrl = process.env.PREVIEW_URL; diff --git a/SECURITY.md b/SECURITY.md index 034e84803..390fb7b2e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,20 +2,15 @@ ## Supported Versions -Use this section to tell people about which versions of your project are -currently being supported with security updates. +We maintain `vX.Y.Z-release-branch` [active areas](https://github.com/we-promise/sure/branches/all?query=release-branch&lastTab=overview) for high impact fixes that need to go out as "hotfix" releases ASAP. | Version | Supported | | ------- | ------------------ | -| 5.1.x | :white_check_mark: | -| 5.0.x | :x: | -| 4.0.x | :white_check_mark: | -| < 4.0 | :x: | +| 0.7.1 | :white_check_mark: | +| 0.7.0 | :white_check_mark: | +| 0.6.x | :x: | ## Reporting a Vulnerability -Use this section to tell people how to report a vulnerability. - -Tell them where to go, how often they can expect to get an update on a -reported vulnerability, what to expect if the vulnerability is accepted or -declined, etc. +Join our Discord and DM @Juanjo there so he can address the vulnerability before +disclosing it. diff --git a/app/assets/tailwind/sure-design-system/components.css b/app/assets/tailwind/sure-design-system/components.css index c1e6af0df..7e96c3645 100644 --- a/app/assets/tailwind/sure-design-system/components.css +++ b/app/assets/tailwind/sure-design-system/components.css @@ -160,6 +160,42 @@ } } + /* + Segmented control (#2137) — track + equal-footprint segments. The selected + state is a single `--active` class so an optional Stimulus controller can + toggle it as one unit. Values mirror the DS tab tokens (tab-bg-group / + tab-item-active / tab-item-hover) but are inlined with `@variant theme-dark` + because `@apply`-ing a custom utility drops its dark override. The near-black + dark track (alpha-black-700) is what makes the gray-700 selected pill read — + the bespoke controls used a too-light `container-inset` track, hence the + audit's "selected pill invisible on dark." Focus = neutral outline (matches + the canonical focus ring; inlined until that token lands on main). + */ + .segmented-control { + @apply inline-flex items-center gap-0.5 p-1 rounded-lg bg-gray-50; + @variant theme-dark { + @apply bg-alpha-black-700; + } + } + + .segmented-control__segment { + @apply inline-flex items-center justify-center px-2 py-1 rounded-md whitespace-nowrap; + @apply text-sm font-medium text-secondary cursor-pointer transition-colors duration-200; + @apply hover:bg-gray-200; + @apply focus-visible:outline-2 focus-visible:outline-offset-2; + @apply focus-visible:outline-alpha-black-400 theme-dark:focus-visible:outline-alpha-white-400; + @variant theme-dark { + @apply hover:bg-gray-800; + } + } + + .segmented-control__segment--active { + @apply bg-white text-primary shadow-sm hover:bg-white; + @variant theme-dark { + @apply bg-gray-700 hover:bg-gray-700; + } + } + /* Horizontally scrollable table wrapper (#2137). `overflow-x: auto` so wide tables scroll instead of clipping (the LLM-usage table was `overflow-hidden` diff --git a/app/components/DS/buttonish.rb b/app/components/DS/buttonish.rb index 653151f95..9d43d1aff 100644 --- a/app/components/DS/buttonish.rb +++ b/app/components/DS/buttonish.rb @@ -34,16 +34,25 @@ class DS::Buttonish < DesignSystemComponent } }.freeze + # Icon-only containers share a height rail with the text buttons of the + # same size (sm ≈ 28px, md ≈ 36px, lg ≈ 48px), so a mixed row — icon + # trigger next to text buttons, the most common header layout — lines up + # instead of mixing 32/44px squares with 36px buttons. + # + # pointer-coarse restores the 44px square on touch devices: the visual + # rail is a pointer-precision tradeoff, and WCAG 2.5.5's 44x44 target + # minimum is about fingers, not mice. Coarse-pointer users get the full + # target; fine-pointer users get the aligned row. SIZES = { sm: { container_classes: "px-2 py-1", - icon_container_classes: "inline-flex items-center justify-center w-8 h-8", + icon_container_classes: "inline-flex items-center justify-center w-7 h-7 pointer-coarse:w-11 pointer-coarse:h-11", radius_classes: "rounded-md", text_classes: "text-sm" }, md: { container_classes: "px-3 py-2", - icon_container_classes: "inline-flex items-center justify-center w-11 h-11", + icon_container_classes: "inline-flex items-center justify-center w-9 h-9 pointer-coarse:w-11 pointer-coarse:h-11", radius_classes: "rounded-lg", text_classes: "text-sm" }, @@ -77,7 +86,13 @@ class DS::Buttonish < DesignSystemComponent def container_classes(override_classes = nil) class_names( - "font-medium whitespace-nowrap", + # Tailwind v4 preflight sets `cursor: pointer` on all - diff --git a/app/views/goals/index.html.erb b/app/views/goals/index.html.erb index 3c3e69b95..8d927234b 100644 --- a/app/views/goals/index.html.erb +++ b/app/views/goals/index.html.erb @@ -1,4 +1,8 @@ -
+<%# @container so the KPI strip + card grids respond to the actual content + width (which shrinks when the account / AI sidebars are open) rather than + the viewport. Viewport breakpoints kept 3 columns in a narrow center + column and crushed the cards. %> +

<%= t(".title") %>

@@ -11,7 +15,7 @@ <%= render "empty_state", linkable_account_count: @linkable_account_count %> <% else %> <%# KPI strip. Contributed last 30d (with prior-30d comparison) / Needs / On track %> -
+

<%= t(".kpi.contributed_label") %>

@@ -117,7 +121,11 @@ action: "input->goals-filter#filter" } ) %> -

+ <%# overflow-x-auto + nowrap chips: the segmented control scrolls + horizontally when the column is too narrow for all six chips, + rather than wrapping "On track" mid-label or forcing the row + wider than the viewport. %> +
<% %w[all on_track behind no_target_date paused completed].each do |status| %> <% active = status == "all" %> <% end %> @@ -139,7 +147,7 @@ · <%= @grid_goals.size %>
-
+
<% @grid_goals.each do |goal| %> <%= render Goals::CardComponent.new(goal: goal) %> <% end %> @@ -179,7 +187,7 @@ <%= @archived_goals.size %> <% end %> -
+
<% @archived_goals.each do |goal| %> <%= render Goals::CardComponent.new(goal: goal, filterable: false) %> <% end %> diff --git a/app/views/goals/show.html.erb b/app/views/goals/show.html.erb index 92a482526..bffcfdf84 100644 --- a/app/views/goals/show.html.erb +++ b/app/views/goals/show.html.erb @@ -224,7 +224,7 @@ <% end %>
-
" diff --git a/app/views/import/configurations/_merchant_import.html.erb b/app/views/import/configurations/_merchant_import.html.erb new file mode 100644 index 000000000..7f4efc1fa --- /dev/null +++ b/app/views/import/configurations/_merchant_import.html.erb @@ -0,0 +1,14 @@ +<%# locals: (import:) %> + +
+

<%= t("import.configurations.merchant_import.description") %>

+ + <%= styled_form_with model: import, + url: import_configuration_path(import), + scope: :import, + method: :patch, + class: "space-y-3" do |form| %> +

<%= t("import.configurations.merchant_import.instructions") %>

+ <%= form.submit t("import.configurations.merchant_import.button_label"), disabled: import.complete? %> + <% end %> +
\ No newline at end of file diff --git a/app/views/imports/new.html.erb b/app/views/imports/new.html.erb index 78950bd76..722ec9106 100644 --- a/app/views/imports/new.html.erb +++ b/app/views/imports/new.html.erb @@ -225,6 +225,16 @@ enabled: true %> <% end %> + <% if params[:type].nil? || params[:type] == "MerchantImport" %> + <%= render "imports/import_option", + type: "MerchantImport", + icon_name: "store", + icon_bg_class: "bg-orange-500/5", + icon_text_class: "text-orange-500", + label: t(".import_merchants"), + enabled: true %> + <% end %> + <% if params[:type].nil? || params[:type] == "RuleImport" %> <%= render "imports/import_option", type: "RuleImport", diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 39077cc6a..5a00adda6 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -60,7 +60,7 @@ end %>
+ <%= render DS::Button.new( + text: t("shared.sync_toast.refresh"), + variant: "secondary", + size: "sm", + icon: "refresh-cw", + type: "button", + data: { action: "sync-toast#refresh" } + ) %>
-
- +
+ <%= render DS::Button.new( + variant: "icon", + size: "sm", + icon: "x", + type: "button", + "aria-label": t("defaults.common.close"), + data: { action: "click->element-removal#remove" } + ) %>
diff --git a/app/views/transactions/_form.html.erb b/app/views/transactions/_form.html.erb index 1064da60f..acf69714f 100644 --- a/app/views/transactions/_form.html.erb +++ b/app/views/transactions/_form.html.erb @@ -1,7 +1,6 @@ -<%# locals: (entry:, categories:) %> +<%# locals: (entry:, account_currencies:, manual_accounts:, categories:, merchants:, tags:) %> -<% account_currencies = Current.family.accounts.map { |a| [a.id, a.currency] }.to_h.to_json %> -<%= styled_form_with model: entry, url: transactions_path, class: "space-y-4", data: { controller: "transaction-form", transaction_form_exchange_rate_url_value: exchange_rate_path, transaction_form_account_currencies_value: account_currencies } do |f| %> +<%= styled_form_with model: entry, url: transactions_path, class: "space-y-4", data: { controller: "transaction-form", transaction_form_exchange_rate_url_value: exchange_rate_path, transaction_form_account_currencies_value: account_currencies.to_json } do |f| %> <% if entry.errors.any? %> <%= render "shared/form_errors", model: entry %> <% end %> @@ -19,7 +18,7 @@ <% if @entry.account_id %> <%= f.hidden_field :account_id, data: { transaction_form_target: "account" } %> <% else %> - <%= f.collection_select :account_id, accessible_accounts.manual.active.alphabetically, :id, :name, { prompt: t(".account_prompt"), label: t(".account"), selected: Current.user.default_account_for_transactions&.id, variant: :logo }, required: true, class: "form-field__input text-ellipsis", data: { transaction_form_target: "account", action: "change->transaction-form#checkCurrencyDifference" } %> + <%= f.collection_select :account_id, manual_accounts, :id, :name, { prompt: t(".account_prompt"), label: t(".account"), selected: Current.user.default_account_for_transactions&.id, variant: :logo }, required: true, class: "form-field__input text-ellipsis", data: { transaction_form_target: "account", action: "change->transaction-form#checkCurrencyDifference" } %> <% end %> <%= f.money_field :amount, @@ -87,7 +86,7 @@
<%= f.fields_for :entryable do |ef| %> <%= ef.collection_select :merchant_id, - Current.family.available_merchants_for(Current.user).alphabetically, + merchants, :id, :name, { include_blank: t(".none"), label: t(".merchant_label"), @@ -96,7 +95,7 @@ menu_placement: :auto } %> <%= render DS::TagSelect.new( form: ef, - tags: Current.family.tags.alphabetically, + tags: tags, selected_ids: ef.object.tag_ids ) %> <% end %> diff --git a/app/views/transactions/_transaction.html.erb b/app/views/transactions/_transaction.html.erb index 9aff39d03..9752d6117 100644 --- a/app/views/transactions/_transaction.html.erb +++ b/app/views/transactions/_transaction.html.erb @@ -7,7 +7,7 @@ <%= turbo_frame_tag dom_id(transaction) do %>
"> -
+
<%= check_box_tag dom_id(entry, "selection"), disabled: transaction.transfer.present?, class: "checkbox checkbox--light hidden lg:block", @@ -179,7 +179,7 @@
-