Index: src/db.c
==================================================================
--- src/db.c
+++ src/db.c
@@ -1872,10 +1872,31 @@
   return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName);
 }
 void db_lset_int(const char *zName, int value){
   db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%d)", zName, value);
 }
+
+/*
+** Returns non-0 if the database (which must be open) table identified
+** by zTableName has a column named zColName (case-sensitive), else
+** returns 0.
+*/
+int db_table_has_column( char const *zTableName, char const *zColName ){
+  Stmt q = empty_Stmt;
+  int rc = 0;
+  db_prepare( &q, "PRAGMA table_info(%Q)", zTableName );
+  while(SQLITE_ROW == db_step(&q)){
+    /* Columns: (cid, name, type, notnull, dflt_value, pk) */
+    char const * zCol = db_column_text(&q, 1);
+    if(0==fossil_strcmp(zColName, zCol)){
+      rc = 1;
+      break;
+    }
+  }
+  db_finalize(&q);
+  return rc;
+}
 
 /*
 ** Record the name of a local repository in the global_config() database.
 ** The repository filename %s is recorded as an entry with a "name" field
 ** of the following form:

Index: src/info.c
==================================================================
--- src/info.c
+++ src/info.c
@@ -1723,11 +1723,11 @@
   const char *zUuid;
   char zTktName[UUID_SIZE+1];
   Manifest *pTktChng;
   int modPending;
   const char *zModAction;
-
+  char *zTktTitle;
   login_check_credentials();
   if( !g.perm.RdTkt ){ login_needed(); return; }
   rid = name_to_rid_www("name");
   if( rid==0 ){ fossil_redirect_home(); }
   zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
@@ -1752,10 +1752,13 @@
     }
     if( strcmp(zModAction,"approve")==0 ){
       moderation_approve(rid);
     }
   }
+  zTktTitle = db_table_has_column( "ticket", "title" )
+      ? db_text("(No title)", "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName)
+      : 0;
   style_header("Ticket Change Details");
   style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
   style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
   style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
   style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
@@ -1776,17 +1779,22 @@
   modPending = moderation_pending(rid);
   if( modPending ){
     @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
   }
   @ <tr><th>Ticket:</th>
-  @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a></td></tr>
+  @ <td>%z(href("%R/tktview/%s",zTktName))%s(zTktName)</a>
+  if(zTktTitle){
+        @<br>%h(zTktTitle)
+  }
+  @</td></tr>
   @ <tr><th>Date:</th><td>
   hyperlink_to_date(zDate, "</td></tr>");
   @ <tr><th>User:</th><td>
   hyperlink_to_user(pTktChng->zUser, zDate, "</td></tr>");
   @ </table>
   free(zDate);
+  free(zTktTitle);
   
   if( g.perm.ModTkt && modPending ){
     @ <div class="section">Moderation</div>
     @ <blockquote>
     @ <form method="POST" action="%R/tinfo/%s(zUuid)">

Index: src/rss.c
==================================================================
--- src/rss.c
+++ src/rss.c
@@ -22,18 +22,37 @@
 #include "rss.h"
 #include <assert.h>
 
 /*
 ** WEBPAGE: timeline.rss
+** URL:  /timeline.rss/y=TYPE&n=LIMIT&tkt=UUID&tag=TAG&wiki=NAME&name=FILENAME
+**
+** Produce an RSS feed of the timeline.
+**
+** TYPE may be: all, ci (show checkins only), t (show tickets only),
+** w (show wiki only). LIMIT is the number of items to show.
+**
+** tkt=UUID filters for only those events for the specified ticket. tag=TAG
+** filters for a tag, and wiki=NAME for a wiki page. Only one may be used.
+**
+** In addition, name=FILENAME filters for a specific file. This may be
+** combined with one of the other filters (useful for looking at a specific
+** branch).
 */
+
 void page_timeline_rss(void){
   Stmt q;
   int nLine=0;
   char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
   Blob bSQL;
   const char *zType = PD("y","all"); /* Type of events.  All if NULL */
+  const char *zTicketUuid = PD("tkt",NULL);
+  const char *zTag = PD("tag",NULL);
+  const char *zFilename = PD("name",NULL);
+  const char *zWiki = PD("wiki",NULL);
   int nLimit = atoi(PD("n","20"));
+  int nTagId;
   const char zSQL1[] =
     @ SELECT
     @   blob.rid,
     @   uuid,
     @   event.mtime,
@@ -62,10 +81,11 @@
     if( !g.perm.Read ){
       if( g.perm.RdTkt && g.perm.RdWiki ){
         blob_append(&bSQL, " AND event.type!='ci'", -1);
       }else if( g.perm.RdTkt ){
         blob_append(&bSQL, " AND event.type=='t'", -1);
+        
       }else{
         blob_append(&bSQL, " AND event.type=='w'", -1);
       }
     }else if( !g.perm.RdWiki ){
       if( g.perm.RdTkt ){
@@ -76,10 +96,46 @@
     }else if( !g.perm.RdTkt ){
       assert( !g.perm.RdTkt &&& g.perm.Read && g.perm.RdWiki );
       blob_append(&bSQL, " AND event.type!='t'", -1);
     }
   }
+
+  if( zTicketUuid ){
+    nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
+      zTicketUuid);
+    if ( nTagId==0 ){
+      nTagId = -1;
+    }
+  }else if( zTag ){
+    nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'",
+      zTag);
+    if ( nTagId==0 ){
+      nTagId = -1;
+    }
+  }else if( zWiki ){
+    nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'",
+      zWiki);
+    if ( nTagId==0 ){
+      nTagId = -1;
+    }
+  }else{
+    nTagId = 0;
+  }
+
+  if( nTagId==-1 ){
+    blob_appendf(&bSQL, " AND 0");
+  }else if( nTagId!=0 ){
+    blob_appendf(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref"
+      " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId);
+  }
+
+  if( zFilename ){
+    blob_appendf(&bSQL,
+      " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)",
+        zFilename, filename_collation()
+    );
+  }
 
   blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );
 
   cgi_set_content_type("application/rss+xml");