Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
My use of Taskwarrior

Getting Started

My Usage

I've been using this for several years now, so here are some of the ways I have set it up to be most productive. See my taskrc below for implementation details.

In general, I've had the most success by keeping lists of tasks short and to the point, avoiding the anxiety of seeing 100 tasks and feeling like I'm going to drown. It serves three purposes:

  • Ensures I do the things I'm responsible for
  • Keeps me from getting anxious about the things I'm responsible for
  • Helps me to not get lost focusing on one top-priority thing at the expense of others

Trust (but occasionally verify) Taskwarrior to keep things in order. Plan the work, work the plan.

This is basically the "Getting Things Done" approach: filter everything into a list, spending minimal time on tasks until they reach the top of the list. It means I can skim through my email or bugspam quickly without worrying that I'll miss or forget something, and without feeling like I need to handle any of it "right now".

Task Views and Aliases

I keep a shell open just for use with Taskwarrior (and for accessing other projects). I use tt (source below) for a quick way to show the current tasklist. Just running tt will re-display the last tasklist. Running tt <name> will run a particular query, e.g., tt moz.

This also adds a shell prompt that displays the currently active tasks in the terminal title.

moz

This view is designed to just show me actionable tasks, in order of urgency. It includes only the moz project (so nothing personal) and omits:

  • ideas (idea tag)
  • tasks up for review (review tag or github_r_)
  • tasks that aren't due soon (I don't use the "due" feature very much)
  • tasks that are waiting
  • tasks that are blocked on another task

oneonone

When it comes time for my one on one, I run something like task oneonone modified.after:2018-11-20 to display all of the tasks that have been modified since my last meeting. I then sort this list by topic (and drop uninteresting things) and paste it into the meeting notes for discussion. This is a nice, quick way to take a retrospective look at the week.

Special Tags

Taskwarrior automatically recognizes the +next tag and massively increases the priority of tagged tasks. I use this frequently for things I need to accomplish immediately, before any other work. For example, if someone pings me in irc with a request or I agree to do something in a meeting, I'll often add it with task add +next 'whatever'. Usually that's a quick thing, perhaps involving creating a bug or sending an email.

When I have completed a task and put it up for review, I tag it +review. This makes the task disappear from the default view. Once the review is complete, Bugwarrior will delete the task. Otherwise, I'll catch it in a weekly triage.

If I've started a task and will benefit from finishing it sooner rather than later, I'll tag it +inprogress. This just bumps the urgency a bit. I often use this when a reviewer requests changes (task 123 modify -review +inprogress). The +land tag is similar; I generally use it when a review is approved but requires more than clicking "Merge".

I tag anything requiring me to respond to someone else as +respond. This is automatically added by review requests, needinfos, and starred emails.

The +tracker and +deliverable tags have very low urgency, causing those items to sink to the bottom of the list. This lets me keep a list of the trackers I'm responsible for, and the deliverables I've promised, without thinking about them daily.

I use the +plane and +idea tags for tasks that are not critical, so they have lower urgency. The latter is for tasks that might make for good hacking on a plane -- pretty clear what to do, and easily completed in a short time. The former is for vague thoughts - I treat it as a way to scrawl an idea down so it's not lost to time. Most of my ideas aren't very good 😀

Today

I use +today as a replacement for hand-drawn lists of tasks to do today -- mostly in my personal tasks. I have a crontask that runs daily on weekdays to clear the tag from all tasks, leaving me to re-create the list of "today" tasks tomorrow. My default view on the Android app is the today query, which shows personal tasks due soon or tagged +today or +next or +inprogress.

Dependencies and Waiting

When a task bubbles up and I think "huh, I can't work on that right now", I generally try to make it disappear. One way to do this is to indicate that it depends on another task assigned to me (task 123 modify depends:99). However, if it depends on something that Taskwarrior is not tracking (such as a bug assigned to someone else), I will set it to wait and appropriate length of time (usually a few days to a week - task 123 modify wait:1wk).

Recurring Triage

Trust, but verify. I don't want to miss something just because I forgot to tag it correctly or mistyped a task number. So I have weekly recurring tasks to triage my task list. To accomplish this, I look at all of the tasks in the project (task proj:moz) and just scan the list, looking for anything surprising.

Surprises might be of the form "oh, that's important!" in which case I'll adjust tags, priority, etc. as necessary. Or they might be of the form "I thought that was done" -- usually a PR I've put out for review, or a needinfo request that has not been resolved.

I also have recurring tasks to check my Phabricator queue (since Phabricator's API is pretty awful) and to write docs or a blog post.

Bugzilla and Github Syncs

My strategy with Bugwarrior is this: sync everything that's assigned to me, and separately sync everything that requires a response from me -- that's the origin of the +respond tag.

I adjust the description template for my syncs to generate nice, clickable URLs in the descriptions. That means I can quickly type task 123 start and then click the link in the displayed info to get started.

Note that Bugwarrior does not support syncing Bugzilla dependencies, and I don't make a special effort to synchronize them manually except the above.

Taskwarrior Features

Separate from how I do things, here are some of the more useful features of Taskwarrior:

Projects and Tags

Use projects to separate unrelated sets of tasks (for example, I have a "moz" project and use the default "" for personal stuff).

Use tags to identify categories of tasks and make them easier to search for, or to affect urgency.

Recurrence and Dependencies

Set up recurring tasks for things you need to do regularly, such as triage.

Dependencies beteween tasks need some additional setup to be useful, but can help hide tasks from view until they are actionable.

Urgency

Urgency is awesome, once you get it tuned. Task lists are displayed in order by urgency, so you can just work on the top task in the list at any one time.

Urgency calculations are based on priority, tags, dependencies, time since the task was created, and a number of other attributes. You can add custom urgencies for tags.

Annotations

Annotations are extra, datestamped bits of information on a task. For tasks synced from somewhere else, these aren't especially useful (and I disable them). But for tasks that involve a lot of phone calls, emails, and waiting, it can be really useful to keep a tiny log of what's happened so far:

task 123 start
Starting task 123 fix Azure billing email
# ..email service..
task 123 annotate "emailed service address"
task 123 stop wait:2d

Bugwarrior

Bugwarrior syncs Taskwarrior to other services. Set it up to run in crontask (I run it once an hour). See the attached bugwarriorrc for details on how I've configured these.

Bugzilla

Bugwarrior can sync any Bugzilla query. However, syncing the same bug with two different Bugwarrior configs will cause issues, so structure your queries carefully.

Github

Bugwarrior can sync both Github issues and PRs.

Phabricator

This sync is not very good -- Phabricator's API is lame (and in particular has no way to show you requested reviews) so Bugwarrior does what it can.

Gmail

This required some weird steps to create a JSON file that probably gives total, indefinite access to my email account. I use it to sync starred emails so that I can simply hit "s" on an email and know it will show up on my task list.

Sync

Use FreeCinc!

You will generate some keys that you need to distribute to all sync'd instances of Taskwarrior. Configure them as instructed.

Then run task sync init on the system that has all of your tasks on it. On the other systems, just run task sync.

Synchronization occurs by tracking changes made on each system and applying those changes on other systems. This does a pretty good job of "merging" changes, even to the same task.

NOTE: if you set up sync, be sure that only one host handles recurrence, otherwise you will get multiple copies of recurring tasks. I accomplish this with an Ansible conditional in the taskrc template below.

Mobile

https://play.google.com/store/apps/details?id=kvj.taskw

This is a thin wrapper around the command-line tool, and will require some use of adb to set it up (copying the sync keys there). Sync setup instructions are here.

[general]
targets = mozilla_phab, bugzilla_mozilla, bugzilla_mozilla_respond, github_assigned_moz, github_respond_moz, github_assigned_personal, github_respond_personal, work_gmail, personal_gmail
annotation_links = True
annotation_comments = False
log.level = DEBUG
legacy_matching = False
[github_assigned_moz]
{% raw -%}
service = github
github.username = djmitche
github.login = djmitche
github.query = is:open is:issue assignee:djmitche user:taskcluster user:mozilla user:mozilla-frontend-infra
github.include_user_issues = False
github.include_user_repos = False
github.import_labels_as_tags = True
github.label_template = github_{{label}}
github.default_priority = M
github.project_template = moz
github.description_template = {{githuburl}} {{githubtitle}}
{% endraw -%}
github.password = {{ bugwarrior_github_password }}
[github_respond_moz]
{% raw -%}
service = github
github.username = djmitche
github.login = djmitche
github.query = is:open is:pr review-requested:djmitche user:taskcluster user:mozilla user:mozilla-frontend-infra
github.include_user_issues = False
github.include_user_repos = False
github.import_labels_as_tags = True
github.label_template = github_{{label}}
github.add_tags = respond
github.default_priority = M
github.project_template = moz
github.description_template = {{githuburl}} {{githubtitle}}
{% endraw -%}
github.password = {{ bugwarrior_github_password }}
[github_assigned_personal]
{% raw -%}
service = github
github.username = djmitche
github.login = djmitche
github.query = is:open is:issue assignee:djmitche -user:taskcluster -user:mozilla -user:mozilla-frontend-infra
github.include_user_issues = False
github.include_user_repos = False
github.import_labels_as_tags = True
github.label_template = github_{{label}}
github.default_priority = M
github.project_template =
github.description_template = {{githuburl}} {{githubtitle}}
{% endraw -%}
github.password = {{ bugwarrior_github_password }}
[github_respond_personal]
{% raw -%}
service = github
github.username = djmitche
github.login = djmitche
github.query = is:open is:pr review-requested:djmitche -user:taskcluster -user:mozilla -user:mozilla-frontend-infra
github.include_user_issues = False
github.include_user_repos = False
github.import_labels_as_tags = True
github.label_template = github_{{label}}
github.add_tags = respond
github.default_priority = M
github.project_template =
github.description_template = {{githuburl}} {{githubtitle}}
{% endraw -%}
github.password = {{ bugwarrior_github_password }}
[bugzilla_mozilla]
{% raw -%}
service = bugzilla
bugzilla.base_uri = bugzilla.mozilla.org
bugzilla.ignore_cc = True
# assigned
bugzilla.query_url = https://bugzilla.mozilla.org/query.cgi?list_id=13470940&emailtype1=exact&emailassigned_to1=1&email1=dustin%40mozilla.com&resolution=---&query_format=advanced
bugzilla.add_tags = bugzilla
bugzilla.project_template = moz
bugzilla.default_priority = H
bugzilla.description_template = http://bugzil.la/{{bugzillabugid}} {{bugzillasummary}}
{% endraw -%}
bugzilla.username = {{ bugwarrior_bugzilla_username }}
bugzilla.api_key = {{ bugwarrior_bugzilla_api_key }}
[bugzilla_mozilla_respond]
{% raw -%}
service = bugzilla
bugzilla.base_uri = bugzilla.mozilla.org
bugzilla.ignore_cc = True
# ni?, f?, r?, not assigned
bugzilla.query_url = https://bugzilla.mozilla.org/query.cgi?j_top=OR&list_id=13320900&emailtype1=notequals&emailassigned_to1=1&o4=equals&email1=dustin%40mozilla.com&v4=dustin%40mozilla.com&o7=equals&v6=review%3F&f8=flagtypes.name&j5=OR&o6=equals&v7=needinfo%3F&f4=requestees.login_name&query_format=advanced&f3=OP&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&f5=OP&v8=feedback%3F&f6=flagtypes.name&f7=flagtypes.name&o8=equals
bugzilla.add_tags = bugzilla, respond
bugzilla.project_template = moz
bugzilla.description_template = http://bugzil.la/{{bugzillabugid}} {{bugzillasummary}}
{% endraw -%}
bugzilla.username = {{ bugwarrior_bugzilla_username }}
bugzilla.api_key = {{ bugwarrior_bugzilla_api_key }}
[personal_gmail]
{% raw -%}
service = gmail
# note that has:red-star doesn't work (perhaps it applies to messages only?)
gmail.query = is:starred label:INBOX
gmail.login_name = djmitche@gmail.com
gmail.description_template = {{gmailsubject}}
gmail.add_tags = email, respond
{% endraw -%}
[work_gmail]
{% raw -%}
service = gmail
gmail.query = is:starred label:INBOX
gmail.login_name = dmitchell@mozilla.com
gmail.description_template = {{gmailsubject}}
gmail.project_template = moz
gmail.add_tags = email, respond
{% endraw -%}
[mozilla_phab]
{% raw -%}
service = phabricator
phabricator.use_maniphest = False
phabricator.user_phids=PHID-USER-sdnm2ejgv4a4tscrqw3m
phabricator.project_template = moz
phabricator.ignore_cc = True
phabricator.ignore_author = True
phabricator.add_tags = phabricator, respond
phabricator.description_template = {{phabricatorurl}} {{phabricatortitle}}
{% endraw -%}
#! /bin/bash
cd ~
search_file=".task/task-pane-search"
refresh_file=".task/task-pane-refresh"
output_file=".task/task-pane-output"
inputs=".task/*.data $search_file $refresh_file"
task_pane=$(readlink -f ~/bin/task-pane)
this_pane=$(tmux list-panes -F "#{pane_id} #{pane_start_command}" | grep "^%[0-9]* $task_pane$" | cut -d' ' -f 1)
[ -f "$search_file" ] || touch "$search_file"
[ -f "$refresh_file" ] || touch "$refresh_file"
(
echo "refresh"
while true; do
inotifywait -q -q -e modify $inputs
echo "refresh"
done
# pipe these two subshells together so that a line from the first leads to a loop of the second, and
# those queue up such as when several inotifywait changes occur.
) | (
while read _; do
# script does not copy the terminal column width, so set it manually
cols=`tput cols`
echo -n $'\033[H..'
script -q -c "stty cols $cols; task rc.gc=no rc.indent.report=4 rc.verbose= $(< $search_file)" $output_file >/dev/null </dev/null
clear
cat $output_file | grep -v '^Script' | grep -v '^$'
lines=$(wc -l < $output_file)
tmux resize-pane -t $this_pane -y $(( lines - 2 ))
tmux set set-titles-string " $(task rc.gc=no rc.indent.report=4 rc.verbose= rc.report.next.columns=description.desc rc.report.next.labels= rc.defaultwidth=1000 next +ACTIVE 2>/dev/null </dev/null | sed -e 's/^ */«/' | sed 's/$/»/' | tr '\n' ' ')"
done
)
# [Created by task 2.3.0 9/12/2014 14:13:20]
# Taskwarrior program configuration file.
# For more documentation, see http://taskwarrior.org or try 'man task', 'man task-faq',
# 'man task-tutorial', 'man task-color', 'man task-sync' or 'man taskrc'
# Here is an example of entries that use the default, override and blank values
# variable=foo -- By specifying a value, this overrides the default
# variable= -- By specifying no value, this means no default
# #variable=foo -- By commenting out the line, or deleting it, this uses the default
# Use the command 'task show' to see all defaults and overrides
# Files
data.location=~/.task
###############################################################################
# Sample taskwarrior 1.9 (or later) dark 256-color theme, featuring green.
#
# taskwarrior - a command line task list manager.
#
# Copyright 2006-2014, Paul Beckingham, Federico Hernandez.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# http://www.opensource.org/licenses/mit-license.php
#
###############################################################################
color=on
color.header=rgb031
color.footnote=rgb031
color.error=rgb031
color.debug=rgb031
color.summary.bar=white on rgb030
color.summary.background=white on color0
color.history.add=color0 on rgb010
color.history.done=color0 on rgb030
color.history.delete=color0 on rgb050
color.burndown.pending=on rgb010
color.burndown.started=on rgb030
color.burndown.done=on gray4
color.sync.added=gray4
color.sync.changed=rgb030
color.sync.rejected=red
color.undo.before=rgb031
color.undo.after=rgb053
color.calendar.today=color0 on rgb151
color.calendar.due=color0 on color249
color.calendar.due.today=color0 on color225
color.calendar.overdue=color0 on color255
color.calendar.weekend=on color235
color.calendar.holiday=rgb151 on rgb020
color.calendar.weeknumber=rgb010
color.recurring=rgb151
color.overdue=color255
color.due.today=color252
color.due=color249
color.active=bold white on rgb012
color.uda.priority.none=
color.uda.priority.H=rgb151
color.uda.priority.M=rgb040
color.uda.priority.L=rgb030
color.tagged=none
color.blocked=color249
color.blocking=rgb240
color.project.none=
color.tag.none=
color.alternate=on color233
color.tag.next=white on rgb001
color.tag.inprogress=white on rgb001
####
report.next.columns=id,project,priority,due,start.active,entry.age,urgency,description.desc,tags
report.next.labels=ID,Proj,Pri,Due,A,Age,Urg,Description,Tags
report.ready.columns=id,project,priority,due,start.active,entry.age,urgency,description,tags
report.ready.labels=ID,Proj,Pri,Due,A,Age,Urg,Description,Tags
# use like task oneonone modified.after:2014-12-18
report.oneonone.description=Mozilla - One-on-One
report.oneonone.columns=description.desc
report.oneonone.labels=Description
report.oneonone.filter=proj:moz ( status:completed or ( status:pending +ACTIVE ) or ( status:pending +review ) or status:waiting ) description.hasnt:triage
report.oneonone.sort=description
report.moz.description=Mozilla - To-Do
report.moz.columns=id,priority,start.active,urgency,due,description.desc,tags
report.moz.labels=ID,Pri,A,Urg,Due,Description,Tags
report.moz.filter=proj:moz -idea -github_r_ -review status:pending limit:30 ( due.before:tomorrow or due: ) -WAITING -BLOCKED
report.moz.sort=due,urgency-
report.mozcheck.description=Mozilla - Check
report.mozcheck.columns=id,priority,start.active,urgency,description.desc,tags
report.mozcheck.labels=ID,Pri,A,Urg,Description,Tags
report.mozcheck.filter=proj:moz status:pending ( +WAITING or +review or +github_r_ or +idea )
report.mozcheck.sort=urgency-
report.personal.description=Personal - To-Do
report.personal.columns=id,priority,start.active,urgency,due,description.desc,tags
report.personal.labels=ID,Pri,A,Urg,Due,Description,Tags
report.personal.filter=( proj: or proj:personal ) ( due.before:tomorrow or due: ) status:pending -WAITING -idea
report.personal.sort=urgency-
report.today.description=Tasks for Today
report.today.columns=id,project,priority,start.active,urgency,due,description.desc,tags
report.today.labels=ID,Proj,Pri,A,Urg,Due,Description,Tags
report.today.filter=status:pending -review and ( ( proj: and ( due.before:tomorrow or +respond or +today or +next or +inprogress) ) or +daytime or ( proj:moz and +today ) )
report.today.sort=urgency-
report.active.description=Active Tasks
report.active.columns=id,description.desc,tags
report.active.labels=ID,Description,Tags
report.active.filter=status:pending +ACTIVE
report.active.sort=urgency-
report.urls.columns=uuid,githubtype,githuburl
report.urls.labels=uuid,githubtype,githuburl
taskd.server=freecinc.com:53589
taskd.key=\/home\/dustin\/.task\/freecinc_12345.key.pem
taskd.certificate=\/home\/dustin\/.task\/freecinc_12345.cert.pem
taskd.ca=\/home\/dustin\/.task\/freecinc_12345.ca.pem
taskd.credentials=FreeCinc\/freecinc_12345\/33e89870-2daa-4438-81f2-8f7daec7d7d7
uda.priority.default=M
## not used anymore
#task.default=personal
priority.default=M
urgency.blocking.coefficient=0
urgency.annotations.coefficient=0
urgency.user.tag.stalled.coefficient=-5
urgency.user.tag.github_stalled.coefficient=-5
urgency.user.tag.land.coefficient=10
urgency.user.tag.respond.coefficient=10
urgency.user.tag.docs.coefficient=1
urgency.user.tag.review.coefficient=-5
urgency.user.tag.tracker.coefficient=-50
urgency.user.tag.deliverable.coefficient=-50
urgency.user.tag.inprogress.coefficient=1.5
urgency.user.tag.today.coefficient=2
urgency.user.tag.plane.coefficient=-2
# relative priority adjustments
urgency.user.tag.r14y.coefficient=3
# recurring tasks' due dates aren't that important (especially before they're due)
urgency.due.coefficient=0.5
# Bugwarrior UDAs
uda.gmailthreadid.type=string
uda.gmailthreadid.label=GMail Thread Id
uda.bugzillasummary.type=string
uda.bugzillasummary.label=Bugzilla Summary
uda.gmaillastsender.type=string
uda.gmaillastsender.label=GMail last sender name
uda.phabricatorid.type=string
uda.phabricatorid.label=Phabricator Object
uda.githubtitle.type=string
uda.githubtitle.label=Github Title
uda.githubbody.type=string
uda.githubbody.label=Github Body
uda.bugzillaurl.type=string
uda.bugzillaurl.label=Bugzilla URL
uda.githuburl.type=string
uda.githuburl.label=Github URL
uda.phabricatorurl.type=string
uda.phabricatorurl.label=Phabricator URL
uda.bugzillastatus.type=string
uda.bugzillastatus.label=Bugzilla Status
uda.bugzillabugid.type=numeric
uda.bugzillabugid.label=Bugzilla Bug ID
uda.githubrepo.type=string
uda.githubrepo.label=Github Repo Slug
uda.bugzillaproduct.type=string
uda.bugzillaproduct.label=Bugzilla Product
uda.githubmilestone.type=string
uda.githubmilestone.label=Github Milestone
uda.phabricatortype.type=string
uda.phabricatortype.label=Phabricator Type
uda.gmailsnippet.type=string
uda.gmailsnippet.label=GMail snippet
uda.githubcreatedon.type=date
uda.githubcreatedon.label=Github Created
uda.githubnumber.type=numeric
uda.githubnumber.label=Github Issue/PR #
uda.gmailsubject.type=string
uda.gmailsubject.label=GMail Subject
uda.gmailurl.type=string
uda.gmailurl.label=GMail URL
uda.bugzillacomponent.type=string
uda.bugzillacomponent.label=Bugzilla Component
uda.gmaillastsenderaddr.type=string
uda.gmaillastsenderaddr.label=GMail last sender address
uda.githubuser.type=string
uda.githubuser.label=Github User
uda.githubupdatedat.type=date
uda.githubupdatedat.label=Github Updated
uda.githubtype.type=string
uda.githubtype.label=Github Type
uda.githubnamespace.type=string
uda.githubnamespace.label=Github Namespace
uda.bugzillaneedinfo.type=date
uda.bugzillaneedinfo.label=Bugzilla Needinfo
uda.gmaillabels.type=string
uda.gmaillabels.label=GMail labels
uda.phabricatortitle.type=string
uda.phabricatortitle.label=Phabricator Title
# END Bugwarrior UDAs
# turn off confirmations
confirmation = no
bulk = 5
recurrence.confirmation = no
{% if not primary %}
# don't process recurrence on this host, since it is not primary
recurrence.limit = 0
{% endif %}
#! /bin/bash
# put the search string into the search file
search_file=".task/task-pane-search"
refresh_file=".task/task-pane-refresh"
if [ $# -gt 0 ]; then
echo "${@}" > $search_file
fi
# start the task pane up if it doesn't exist
task_pane=$(readlink -f ~/bin/task-pane)
matching_pane=$(tmux list-panes -F "#{pane_id} #{pane_start_command}" | grep "^%[0-9]* $task_pane$" | cut -d' ' -f 1)
if [ -z "$matching_pane" ]; then
tmux split-window -p 40 -vbd ~/bin/task-pane
else
date > $refresh_file
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment