{# Shared form fragments. #} {% macro err(errors, name) %} {%- if errors and name in errors %}{{ errors[name] }}{% endif -%} {% endmacro %} {% macro errors_box(errors) %} {%- if errors %}
Could not save:
{%- endif %} {% endmacro %} {% macro filter_control(col, active, form_id) %} {%- if col.kind == 'enum' -%} {%- else -%} {%- endif -%} {% endmacro %} {% macro sort_links(col, sort, dir, sort_url) %} ▲▼ {%- endmacro %} {% macro filter_help() %} ?what does !text mean? Filters (one box per column, combined with AND)
textcontains
!textdoes not contain
=textequals
>5 <5larger / smaller (numbers, dates as 2026-09-01, otherwise text order)
-empty
*not empty
Lists: cmd-click (ctrl-click) to select several. Sorting: ▲ A to Z, ▼ Z to A next to each heading; empty values sort last. Filters, sorting and search stay in the URL, so a view can be bookmarked.
{%- endmacro %} {% macro filter_form(form_id, action, active, q=None, sort='', dir='asc') %}
{% if q is not none %}{% endif %} {% if sort %}{% endif %} {{ filter_help() }} {% if active or sort %}clear filters and sorting{% endif %}
{% endmacro %} {# `fold` (the contact page): a closed box, `open` when it should start open. #} {% macro drafts_section(company, draft_contact, drafts, signal, observation, page, fold=false, open=true) %} {% set heading %}Message drafts{% if draft_contact %} for {{ draft_contact.first_name }}{% endif %} ({{ language_names[drafts[0].language] }}){% endset %} {% if fold %}

{{ heading }}

{% else %}

{{ heading }}

{% endif %}

Three deliberately different angles, no AI. Square brackets are yours to fill. Edit in place, copy, or open it in the log form. The language is the contact's draft language, else the company's country; the picker here changes it for this page only.

{% for d in drafts %}
{{ d.label }}
log as sent
{% endfor %}
{% if fold %}
{% else %}{% endif %} {% endmacro %} {# An interaction is always with a person. With `contact` (the contact page) the person is fixed; elsewhere you pick one. A note has no direction. #} {% macro interaction_form(action, company, values, errors=None, submit='Log interaction', contact=None) %}
{{ errors_box(errors) }}
{% for d in directions %} {% endfor %} {% if contact %} {% else %} {% endif %}
{{ custom_fields(custom_fields_for('interaction'), values, errors) }} {# The body is the record. Custom fields sit beside it and never touch it. #}
{% endmacro %} {# The labels of the user's own fields. `grid`: they sit in a .form-grid (the company form), three columns each, instead of in a .row of their own. #} {% macro custom_labels(defs, values, errors, grid=false) %} {% for f in defs %} {{ f.label }} {%- if f.type == 'select' %} {%- elif f.type == 'date' %} {%- elif f.type == 'number' %} {%- else %} {%- endif %} {{ err(errors, 'custom_' ~ f.key) }} {%- if f.help %} {{ f.help }}{% endif %} {% endfor %} {% endmacro %} {% macro custom_fields(defs, values, errors) %} {%- if defs %}
{{ custom_labels(defs, values, errors) }}
{%- endif %} {% endmacro %} {# `hide`: the fields layout.toml hides on the record page. A form leaves them out entirely, so saving keeps their values (web.submitted_only). #} {# The company form: groups of fields on a 12-column grid (.form-grid, the span-N classes in style.css). `hide` is what layout.toml hides: such a field is left out, and a group with nothing left in it is left out with it. The reason and requalify fields only show for a stage that keeps them, or when they already hold something; they are hidden, not removed, so they still submit. #} {% macro company_fields(values, errors) %} {%- set hide = layout_hidden('company', errors) %} {%- set custom = custom_fields_for('company') | rejectattr('key', 'in', hide) | list %} {%- set keeps = stage_keeps(values.stage) %} {#- which stages keep a reason (and a date to requalify on), for the script below #} {%- set keeps_by_stage = {} %} {%- for s in stages if stage_keeps(s) %}{% set _ = keeps_by_stage.update({s: stage_keeps(s)}) %}{% endfor %} {%- set show_type = type_names() and 'next_step_type' not in hide %}
{#- The name is not a login: tell password managers to leave it alone. #} {% if 'source' not in hide %}{% endif %} {% if 'website' not in hide %}{% endif %} {% if 'linkedin' not in hide %}{% endif %} {% if 'country' not in hide %}{% endif %}
{% if 'lost_reason' not in hide or 'requalify_on' not in hide %} {%- set reason_shown = 'lost_reason' not in hide and (keeps or values.lost_reason or errors.lost_reason) %} {%- set when_shown = 'requalify_on' not in hide and ('requalify' in keeps or values.requalify_on or errors.requalify_on) %}
Closing details
{% if 'lost_reason' not in hide %}{% endif %} {% if 'requalify_on' not in hide %}{% endif %}
{% endif %} {% if 'product_oneliner' not in hide or 'value_eur_month' not in hide or custom %}
Profile
{% if 'product_oneliner' not in hide %}{% endif %} {% if 'value_eur_month' not in hide %}{% endif %} {{ custom_labels(custom, values, errors, grid=true) }}
{% endif %} {% if 'next_step' not in hide or 'next_step_due' not in hide or 'next_step_status' not in hide or show_type %}
Next step
{% if 'next_step' not in hide %}{% endif %} {% if 'next_step_due' not in hide %}{% endif %} {% if 'next_step_status' not in hide %}{% endif %} {% if show_type %}{% endif %}
{% endif %} {% if 'tags' not in hide or 'notes' not in hide %}
Tags and notes
{% if 'tags' not in hide %}{% endif %} {% if 'notes' not in hide %}{% endif %}
{% endif %} {% endmacro %} {% macro contact_fields(values, errors) %} {%- set hide = layout_hidden('contact', errors) %}
{% if 'title' not in hide %}{% endif %} {% if 'role' not in hide %}{% endif %} {% set lang = (values.language or '') | trim | lower %} {% if 'language' not in hide %}{% endif %}
{% if 'email' not in hide or 'phone' not in hide or 'linkedin' not in hide %}
{% if 'email' not in hide %}{% endif %} {% if 'phone' not in hide %}{% endif %} {% if 'linkedin' not in hide %}{% endif %}
{% endif %} {{ custom_fields(custom_fields_for('contact') | rejectattr('key', 'in', hide) | list, values, errors) }} {% endmacro %} {# `contact_page`: every entry is with the same person, so no name; the direction is an arrow, a missing subject says nothing, and a message shows its first line with the rest behind "more". #} {% macro timeline(company, interactions, contact_page=false) %} {% endmacro %} {% macro type_select(current='', autosubmit=false, name='type') -%} {%- set options = type_names() -%} {%- if options -%} {%- endif -%} {%- endmacro %} {% macro type_cell(company, t, index, contact='') -%} {% if type_names() and not t.done %}
{{ type_select(t.type, autosubmit=true) }}
{% else %}{{ type_chip(t.type) }}{% endif %} {%- endmacro %} {% macro type_chip(name) -%} {%- if name -%} {%- set colour = type_colour(name) -%} {{ name }} {%- endif -%} {%- endmacro %} {% macro todo_actions(company, todo, back='', delete=false) -%} {%- if todo.is_field -%}
{% if back %}{% endif %}
{%- if delete %}
{% if back %}{% endif %}
{%- endif %} {%- else -%}
{% if back %}{% endif %}
{%- if delete %}
{% if back %}{% endif %}
{%- endif %} {%- endif -%} {%- endmacro %} {# Edit the text and due date of one open to-do. `index` is None for the company's next-step fields; `contact` is the person's slug or ''. #} {% macro todo_edit(company, t, index, contact='', back='') -%} {%- if not t.done -%}
Edit
{% if back %}{% endif %}
{%- endif -%} {%- endmacro %} {% macro task_section(company, today) -%} {% set first = company.next_todo() %}

Tasks

For the company and its people. The next step is the open task due first; change a due date to change what is next. This company on the Tasks page.

{% if type_names() %}{% endif %} {% for t in company.open_todos() + (company.todos() | selectattr('done') | list) %} {% set is_next = first and not first.done and not t.done and t.key == first.key %} {% if type_names() %}{{ type_cell(company, t, t.index, t.contact.slug if t.contact else '') }}{% endif %} {% else %} {% endfor %}
tasktypeforduestatus
{{ t.text or '(no text)' }}{% if is_next %} next step{% endif %}{% if not type_names() %} {{ type_chip(t.type) }}{% endif %}{% if t.contact %}{{ t.contact.name }}{% else %}company{% endif %} {{ fmt_date(t.due) if t.due else 'no date' }} {% if t.done %}done{% if t.done_on %} {{ fmt_date(t.done_on) }}{% endif %}{% else %}open{% endif %} {{ todo_edit(company, t, t.index, t.contact.slug if t.contact else '') }} {{ todo_actions(company, t, delete=true) }} {% if is_next and t.due %}Add to Google Calendar{% endif %}
No tasks yet.
{% if type_names() %}{% endif %}
{%- endmacro %} {% macro task_list(company, tasks, today, contact='', adding=true) -%}
{% for t in tasks %} {% if type_names() %}{{ type_cell(company, t, loop.index0, contact) }}{% endif %} {% else %} {% endfor %}
{{ t.text }}{% if not type_names() %} {{ type_chip(t.type) }}{% endif %}{{ fmt_date(t.due) if t.due else '' }} {% if t.done %}done{% if t.done_on %} {{ fmt_date(t.done_on) }}{% endif %}{% endif %} {{ todo_edit(company, t, loop.index0, contact) }}
Nothing on the list.
{% if adding %}
{% if type_names() %}{% endif %}
{% endif %} {%- endmacro %} {# The handoff to the user's own agent (Make it yours): a request box and the buttons for the agent config.toml names. static/adjust.js builds the prompt, the link and the command from these data attributes as the user types, the same way adjust.handoff() does; the hint lines below say what each one does. #} {% macro handoff(h, id, page_title='', path='', label='Describe a change', text='', more=false) %}
{% if h.how == 'link' %}{{ h.button }}{% endif %} {% if h.how == 'command' %}{% endif %} {% if more %}More ideas{% endif %}
{% if h.how == 'link' %}{% endif %} {% if h.agent == 'claude' %}

Opens a terminal in your data folder with the request typed in. Nothing runs until you press Enter. Nothing opened? Run claude once in a terminal, then try again.

{% elif h.agent == 'cursor' %}

Open your data folder ({{ h.folder_shown }}) in Cursor first: the request goes to the Cursor window in front. Nothing runs until you press Enter.

{% elif h.how == 'command' %}

Paste the command in a terminal and press Enter: {{ h.label }} starts in your data folder with the request.

{% else %}

Paste the prompt into your AI agent, started in your data folder ({{ h.folder_shown }}). Name your agent to get a one-click button.

{% endif %}
What your agent receives

{% endmacro %}