codeq/asanafeedback

Feedback widget for Neos CMS websites that creates Asana tasks with annotated screenshots

Maintainers

Package info

github.com/code-q-web-factory/CodeQ.AsanaFeedback

Language:JavaScript

Type:neos-package

pkg:composer/codeq/asanafeedback

Transparency log

Statistics

Installs: 43

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.3.1 2026-07-20 15:11 UTC

This package is auto-updated.

Last update: 2026-07-20 15:12:04 UTC


README

WIP This is currently tailored to the use cases for Code Q, feel free to fork your own version of add PRs

A reusable Neos CMS package that adds a visual feedback widget to the rendered website. Visitors capture a screenshot of the current page, annotate it (freehand, rectangle, arrow, text, undo/redo, delete), give the report a title and description — and every submission creates exactly one task in a fixed Asana project, including the annotated screenshot as attachment, the page URL, the author and technical browser context. It replaces Marker.io for this use case.

Features

  • Screenshot of the visible viewport, rendered DOM-based in the browser (html-to-image) with a loading indicator during capture; the widget itself is never part of the screenshot
  • Annotation editor on Fabric.js: freehand, rectangle, arrow, text, undo/redo, remove selection, five colors
  • Direct task creation in a fixed Asana project; the target section is resolved by name (configurable candidate list, e.g. Todo) or by fixed GID
  • Every user can set an optional task title (otherwise the task is named Website-Feedback: <description>) and assign the task to a client visible assignee (visibleToClient: true); submissions without an explicit choice use the configured default assignee
  • Logged-in Neos users are identified server side; their display name is used as author and cannot be overridden by the browser
  • Members of the internal Code Q team (server side allowlist) can pick every configured assignee and get the Asana task link after submission
  • Feedback button in the Neos backend toolbar (next to the dimension switcher) for all logged-in users; its screenshot captures the full backend including the content canvas and inspector, and its technical context includes the live content-canvas URL
  • Optional screencast recording (Screen Capture API, https only), attached to the same task
  • German and English UI via XLIFF resources, following the site language
  • Styled after the Neos CMS backend and hardened against site CSS
  • Rate limiting, server side MIME/size validation and cleanup of temp files

Architecture: the Asana feedback relay

The Neos package never talks to Asana directly and never holds the Asana personal access token. Instead it posts every submission to a small standalone relay script (see RemoteService/) that is deployed once on a central server, e.g. at https://docs.codeq.at/asana-feedback/:

Browser widget ──▶ Neos (CodeQ.AsanaFeedback) ──▶ relay (docs.codeq.at) ──▶ Asana API
                   holds: shared secret           holds: Asana token

The Neos project authenticates against the relay with a shared secret (ASANA_FEEDBACK_ACCESS_TOKEN). The relay accepts exactly one operation — create one feedback task with its attachments — so a compromised website cannot do anything else with the Asana account (no reading tasks, no other projects beyond the optional allowlist, no deleting).

Setup

0. Deploy the relay service (once, centrally)

The relay must be running before any website can submit feedback. Copy the contents of RemoteService/ (index.php, .htaccess, config.example.php) into a folder on the central server, e.g. docs.codeq.at/asana-feedback/, then:

  1. Copy config.example.php to config.php (git-ignored, never committed) and fill in:
    • asanaAccessToken: the Personal Access Token of the dedicated Asana integration user (Asana developer console). The integration user must be a member of every target Asana project.
    • sharedSecret: a strong random value, e.g. openssl rand -hex 32. This is the value the websites use as ASANA_FEEDBACK_ACCESS_TOKEN.
    • allowedProjectGids (optional): allowlist of project GIDs the relay may create tasks in; empty allows every project of the integration user.
  2. Make sure config.php is not served: the shipped .htaccess denies it on Apache; on nginx add an equivalent location block.
  3. The relay must be reachable via HTTPS only.
  4. Allow large uploads: screencasts can be up to 100 MB, so upload_max_filesize/post_max_size need ~128M and max_execution_time ~300s (the .htaccess sets this for mod_php, otherwise configure it in the PHP pool).

A quick smoke test — an unauthenticated request must return 401:

curl -i -X POST https://docs.codeq.at/asana-feedback/

1. Require the package

Install the package from Packagist in the project root:

composer require codeq/asanafeedback

Nothing else needs to be wired up manually: the Fusion integration (autoInclude), the routes, the security policy for the public endpoint, the authentication request pattern and the Neos backend toolbar plugin are all registered by the package itself.

2. Provide the relay shared secret

Provide the relay's shared secret (the sharedSecret from the relay's config.php, not an Asana token) as the environment variable ASANA_FEEDBACK_ACCESS_TOKEN — never in versioned configuration. Locally with ddev, for example:

# .ddev/config.local.yaml (git-ignored)
web_environment:
  - ASANA_FEEDBACK_ACCESS_TOKEN=3f9c2e...

followed by ddev restart. On Proserver/Beach the variable is set through the deployment secret store.

If the relay runs somewhere other than the default https://docs.codeq.at/asana-feedback/, point the package at it:

# DistributionPackages/Vendor.Site/Configuration/Settings.AsanaFeedback.yaml
CodeQ:
  AsanaFeedback:
    feedbackService:
      endpoint: 'https://example.com/asana-feedback/'

3. Configure the Asana project

The only mandatory per-project setting is the Asana project GID (the long number in the project URL):

# DistributionPackages/Vendor.Site/Configuration/Settings.AsanaFeedback.yaml
CodeQ:
  AsanaFeedback:
    asanaProjectGid: '1216274953146548'

Tasks are placed in the first section whose name matches the configured candidate list (Todo, Todos, Organisation — case-insensitive). Make sure the Asana project has such a section, configure your own asanaSectionNames, or pin a fixed asanaSectionGid. If no section can be resolved, submissions fail with a controlled error message.

4. Decide where the frontend widget is visible

enableInFrontend controls whether the widget is rendered on the website for all visitors. The package default is false, but it ships context configuration that enables it in the Development, Production/Proserver/Staging and Production/Beach/Staging Flow contexts. Projects can override this per context in their global configuration, e.g.:

# Configuration/Production/Proserver/Staging/Settings.yaml
CodeQ:
  AsanaFeedback:
    enableInFrontend: false

The decision is cached with the page (disabled sites stay fully cacheable), so changing it requires a content cache flush: ./flow flow:cache:flushone Neos_Fusion_Content.

Independent of this flag, every logged-in Neos user always has the feedback button in the backend toolbar.

Static and headless frontend embeds

Frontends that do not render the Fusion integration can request the same safe frontend configuration from /codeq-asana-feedback/frontend-config?locale=en. The endpoint is available whenever enableInFrontend permits the current visitor to use the widget; the authenticated backend configuration endpoint remains unchanged.

Embed the returned JSON as the text content of a #codeq-asana-feedback-config script element, then load Public/Styles/Widget.css and Public/Scripts/Widget.js. Hosts whose visible content lives in same-origin iframes can add data-include-iframes="true" to the configuration element so the iframe content is composited into the screenshot.

All configuration options

The package defaults (see Configuration/Settings.yaml) already contain the Code Q team mapping; every value can be overridden per project:

CodeQ:
  AsanaFeedback:
    enableInFrontend: false

    feedbackService:
      # relay service that holds the actual Asana access token
      endpoint: 'https://docs.codeq.at/asana-feedback/'
      # shared secret to authenticate against the relay
      accessToken: '%env:ASANA_FEEDBACK_ACCESS_TOKEN%'

    asanaProjectGid: ''
    defaultAssigneeGid: '422230010221' # Roland; used when none is selected
    # optional fixed section; when empty the section is resolved by name:
    asanaSectionGid: ''
    asanaSectionNames: ['Todo', 'Todos', 'Organisation']

    limits:
      screenshotBytes: 10485760      # 10 MB
      videoBytes: 100000000          # Asana attachment limit
      descriptionCharacters: 10000

    rateLimit:                       # per client IP
      maxPerMinute: 5
      maxPerHour: 40

    # Neos account identifiers of the internal team: these users can pick
    # every assignee, get the task link and are named by their Neos account
    teamAccountIdentifiers:
      - 'roland.schuetz'
      - 'felix.gradinaru'
      - 'daniel.schmelz'
      - 'clara.borek'
      - 'michael.koepl'
      - 'dion.holder'

    # selectable assignees; "visibleToClient: true" entries can be picked
    # by every visitor, the others only by team members
    assignees:
      roland:
        label: 'Roland'
        asanaUserGid: '422230010221'
        avatar: 'resource://CodeQ.AsanaFeedback/Public/Images/Team/roland.jpg'
        visibleToClient: true
      yurii:
        label: 'Yurii'
        asanaUserGid: '510973132418883'
        avatar: 'resource://CodeQ.AsanaFeedback/Public/Images/Team/yurii.jpg'
        visibleToClient: false
      daniel:
        label: 'Daniel'
        asanaUserGid: '240050036824008'
        avatar: 'resource://CodeQ.AsanaFeedback/Public/Images/Team/daniel.jpg'
        visibleToClient: false
      clara:
        label: 'Clara'
        asanaUserGid: '1210155270823988'
        avatar: 'resource://CodeQ.AsanaFeedback/Public/Images/Team/clara.jpeg'
        visibleToClient: true
      michael:
        label: 'Michael'
        asanaUserGid: '778955489601506'
        avatar: 'resource://CodeQ.AsanaFeedback/Public/Images/Team/michael.jpeg'
        visibleToClient: true
      arvin:
        label: 'Arvin'
        asanaUserGid: '1213418029127040'
        avatar: 'resource://CodeQ.AsanaFeedback/Public/Images/Team/arvin.jpeg'
        visibleToClient: false
      dion:
        label: 'Dion'
        asanaUserGid: '1215306059024724'
        avatar: 'resource://CodeQ.AsanaFeedback/Public/Images/Team/dion.jpg'
        visibleToClient: true

Security notes

  • The Asana personal access token only exists on the central relay server; the websites merely hold a shared secret whose only capability is creating one feedback task per request. A compromised website cannot read, modify or delete anything in Asana.
  • All Asana communication happens exclusively server side; secret, project, section and assignee GIDs are never delivered to the browser
  • The relay re-validates everything independently: constant time secret comparison, numeric GIDs, optional project allowlist and content sniffed MIME types of the attachments
  • Submitted assignees are validated server side against the allowlist and the visibleToClient flag; project and section can never be chosen by the client
  • The submit endpoint is rate limited per client IP and validates MIME type (content sniffing), file size and description length server side
  • Uploaded files are stored under server generated temporary names and are removed after the transfer, successful or not

Development

The built assets are committed under Resources/Public, deployments need no node step. To rebuild after changes (builds the website widget and the backend toolbar plugin):

cd Resources/Private/JavaScript && npm install
cd ../BackendUi && npm install && cd ../JavaScript
npm run build

Tests:

# unit tests (from the distribution root)
bin/phpunit --bootstrap Build/BuildEssentials/PhpUnit/UnitTestBootstrap.php \
    DistributionPackages/CodeQ.AsanaFeedback/Tests/Unit

# end-to-end tests in Chromium, Firefox and WebKit incl. Asana verification
# (ASANA_ACCESS_TOKEN is a real Asana PAT used to verify the created tasks,
# independent of the relay shared secret the website itself uses)
cd Tests/E2E && npm install playwright && npx playwright install
ASANA_ACCESS_TOKEN=... node run-tests.mjs

Open source dependencies

Library License Purpose
fabric 6.x MIT screenshot annotation canvas
html-to-image 1.x MIT DOM based screenshot rendering
esbuild 0.25.x MIT build tooling (dev only)
@neos-project/neos-ui-extensibility 8.x GPL-3.0 (as Neos UI) backend toolbar plugin shim (dev only, aliases to host UI)
Feather Icons (inlined SVG paths) MIT toolbar and status icons

Versions are pinned via the committed package-lock.json; third-party license texts are linked from the bundle header comment (Widget.js legal comments).